
当相互关联的对象在构造函数中彼此实例化时,容易陷入无限循环。本文探讨了这种循环依赖问题,并提出了一种优雅的解决方案:使用工厂方法结合实例缓存机制。通过将对象创建逻辑封装在静态工厂方法中,并维护一个已实例化对象的缓存,可以确保每个唯一id只对应一个对象实例,从而有效避免重复实例化和无限循环,同时优化资源利用。
在面向对象编程中,当两个或多个类之间存在相互依赖关系时,尤其是在它们的构造函数中直接或间接地实例化对方,就可能导致所谓的“循环引用”或“无限循环”问题。设想有两个模型类 A 和 B,它们之间存在一对多关系:A 包含多个 B,而 B 属于一个 A。
问题的核心在于,如果 A 的构造函数尝试加载其关联的 B 实例,而 B 的构造函数又尝试加载其关联的 A 实例,就会形成一个无限递归的调用链:
以下是原始代码中导致问题的关键部分:
Class B 的构造函数:
立即学习“PHP免费学习笔记(深入)”;
public function __construct(int $id = null)
{
parent::__construct($id);
$a_id = $this->get('a_id'); // 获取关联A的ID
if ($a_id) {
$this->a = new A($a_id); // 在B的构造函数中创建A的实例
}
}Class A 的构造函数及 initB() 方法:
public function __construct(int $id = null)
{
parent::__construct($id);
$this->date = new CarbonPL($this->get('date'));
$this->initB(); // 在A的构造函数中初始化B的实例
}
private function initB()
{
if (!$this->isReferenced()) { // 检查实例是否存在于DB
return;
}
$query = B::getIDQuery();
$query .= ' WHERE is_del IS FALSE';
$query .= ' AND a_id = ' . $this->id;
$ids = Helper::queryIds($query);
foreach ($ids as $id) {
$this->B[] = new B($id); // 在A中创建B的实例
}
}这段代码清晰地展示了 A 依赖 B,B 又依赖 A 的循环。
为了解决这种无限循环问题,一种高效且优雅的模式是使用“工厂方法结合实例缓存”。这种方法的核心思想是:
这样可以确保对于任何给定的ID,系统中只会存在一个对应的对象实例,从而避免了重复创建和循环引用的发生。
1. 修改 Class A
首先,我们修改 A 类,使其构造函数变为私有,并添加一个静态工厂方法 create_for_id。
<?php
class A {
private static $cache = array(); // 静态缓存,用于存储已创建的A实例
// 将构造函数设为私有,防止外部直接实例化
private function __construct( $id ) {
// 父类构造函数调用,如果需要
// parent::__construct( $id );
// 假设这里是A类自身的初始化逻辑
// 注意:这里仍然会调用initB(),但initB()将使用B的工厂方法
$this->id = $id; // 假设id是对象的唯一标识
$this->date = new CarbonPL($this->get('date'));
$this->initB();
}
// 静态工厂方法,用于获取A的实例
public static function create_for_id( $id ) {
if ( isset( self::$cache[ $id ] ) ) {
// 如果缓存中已存在该ID的实例,则直接返回
$result = self::$cache[ $id ];
} else {
// 否则,创建新实例并存入缓存
$result = new A( $id );
self::$cache[ $id ] = $result; // 将新创建的实例存入缓存
}
return $result;
}
// 假设的辅助方法,用于从数据库获取数据
private function get(string $field) {
// 实际应用中这里会根据ID从数据库加载数据
// 简化示例,假设从一个模拟数据源获取
$data = [
1 => ['date' => '2023-01-01', 'a_id' => null],
2 => ['date' => '2023-01-02', 'a_id' => null],
];
return $data[$this->id][$field] ?? null;
}
private function isReferenced() {
// 检查实例是否存在于DB的逻辑
return true; // 简化示例
}
// initB() 方法现在将使用B的工厂方法
private function initB()
{
if (!$this->isReferenced()) {
return;
}
// 假设这里是获取关联B的ID的逻辑
// 实际应用中,会根据A的ID查询B的ID
$b_ids = [1, 2]; // 示例数据,假设A的实例ID为1时关联B的ID为1和2
foreach ($b_ids as $b_id) {
// 关键:这里不再使用 new B($id),而是使用 B::create_for_id($id)
$this->B[] = B::create_for_id($b_id);
}
}
}2. 修改 Class B
B 类也需要类似地修改,使其构造函数私有化,并提供一个静态工厂方法 create_for_id。
<?php
class B {
private static $cache = array(); // 静态缓存,用于存储已创建的B实例
public $a; // 关联的A对象
public $id; // B的ID
// 将构造函数设为私有
private function __construct( $id ) {
// parent::__construct( $id );
$this->id = $id;
$a_id = $this->get('a_id'); // 获取关联A的ID
if ($a_id) {
// 关键:这里不再使用 new A($a_id),而是使用 A::create_for_id($a_id)
$this->a = A::create_for_id($a_id);
}
}
// 静态工厂方法,用于获取B的实例
public static function create_for_id( $id ) {
if ( isset( self::$cache[ $id ] ) ) {
$result = self::$cache[ $id ];
} else {
$result = new B( $id );
self::$cache[ $id ] = $result;
}
return $result;
}
// 假设的辅助方法,用于从数据库获取数据
private function get(string $field) {
// 实际应用中这里会根据ID从数据库加载数据
// 简化示例,假设从一个模拟数据源获取
$data = [
1 => ['a_id' => 1], // B的实例ID为1,关联A的实例ID为1
2 => ['a_id' => 1], // B的实例ID为2,关联A的实例ID为1
];
return $data[$this->id][$field] ?? null;
}
}现在,无论何时你需要一个 A 或 B 的实例,都应该通过它们的静态工厂方法来获取,而不是直接使用 new 关键字:
// 获取ID为1的A实例 $instanceOfA = A::create_for_id(1); // 获取ID为1的B实例 $instanceOfB = B::create_for_id(1); // 此时,如果$instanceOfA在初始化时需要加载关联的B实例, // 它会调用 B::create_for_id()。 // 如果$instanceOfB在初始化时需要加载关联的A实例, // 它会调用 A::create_for_id()。 // 由于缓存机制,即使在A的构造函数中调用 A::create_for_id(1), // 也不会创建新的A实例,而是返回已存在的$instanceOfA。 // 同理,对于B也一样。
优点:
注意事项:
工厂方法结合实例缓存模式是解决对象循环引用导致构造函数无限循环问题的有效策略。它通过控制对象的创建过程,确保每个唯一ID只对应一个对象实例,从而避免了递归实例化,并带来了资源优化的好处。在设计相互依赖的类时,应优先考虑这种模式,以构建更健壮、高效的应用程序。
以上就是解决PHP对象循环引用导致构造器无限循环的策略:工厂方法与实例缓存的详细内容,更多请关注php中文网其它相关文章!
PHP怎么学习?PHP怎么入门?PHP在哪学?PHP怎么学才快?不用担心,这里为大家提供了PHP速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号