【问题标题】:Circular dependency - Injecting objects that are directly depended on each other循环依赖——注入直接相互依赖的对象
【发布时间】:2014-07-27 09:32:13
【问题描述】:

我已经使用Dice PHP DI container 有一段时间了,就注入依赖项的简单性而言,它似乎是最好的。

来自Dice Documentation

class A {
    public $b;
    
    public function __construct(B $b) {
        $this->b = $b;
    }
}

class B {
    
}

$dice = new \Dice\Dice;    
$a = $dice->create('A');
var_dump($a->b); //B object

但是,当你必须使用直接相互依赖的对象时,由于无限循环,最终的结果是服务器错误。

例子:

class A {
    public $b;

    public function __construct(B $b) {
        $this->b = $b;
    }
}

class B {
    public $a;

    public function __construct(A $a) {
        $this->a = $a;
    }
}

Dice 的作者说没有办法从 A 或 B 类构造对象。如:

  • “A”对象需要存在“B”对象才能创建
  • 但“B”对象需要“A”对象存在才能创建

作者说,此限制涉及所有 DI容器


问题:

在不更改初始代码的情况下很好地解决这个问题的最佳解决方案是什么?谁能提供一个使用其他 DI 容器的示例,什么时候可以运行 示例代码 没有笨重解决方法?

【问题讨论】:

  • 花点时间完全忘记容器。您可以自己创建这些对象中的任何一个吗?你已经创建了一个循环依赖,它不能解决它不是容器的错。
  • @Jon 这不是容器的错,是的!但是,如果我从其中一个对象中删除其中一个依赖项,那么它们就可以正常工作。我需要从 B 中使用 A 和 B 同时依赖于 A 的微不足道的使用的问题。我无法很好地解决这个问题。 (我不只是在寻找解决方案)。你真的可以在这里做什么?
  • 将 A 的重要和不重要的功能分成两个独立的实体听起来像是一个计划。可能当前版本的 A 职责太多?
  • @Jon 如果我这样做,那么我的项目结构将开始失去意义。你可以看到它有什么职责的基本概念。 github.com/TomBZombie/Dice/issues/7#issue-35102964
  • 我可以看到类名,但这并不能说明什么。为什么Session需要Language,Language需要Session?

标签: php dependency-injection circular-dependency di-containers


【解决方案1】:

你有一个循环依赖,这很难解决。首先要做的是尝试通过重构您的类及其交互方式来摆脱这种循环依赖

如果你真的做不到,有解决办法。我将从Self-referencing models cause Maximum function nesting level of x in Laravel 4复制粘贴我的答案:

  • Setter 注入

除了在构造函数中注入依赖之外,您还可以将它注入到 setter 中,该 setter 将在构造对象后调用。在伪代码中,它看起来像这样:

$userRepo = new UserRepository();
$cartRepo = new CartRepository($userRepo);
$userRepo->setCartRepo($userRepo);
  • 懒惰注入

我不知道 Dice 是否支持惰性注入,但这也是一种解决方案:容器将注入一个代理对象而不是实际的依赖项。该代理对象仅在访问时才加载依赖项,因此无需在调用构造函数时构建依赖项。

如果您有兴趣,这里解释一下惰性注入的工作原理:http://php-di.org/doc/lazy-injection.html

【讨论】:

    【解决方案2】:

    正如您在 Dice github (https://github.com/TomBZombie/Dice/issues/7) 上的帖子所述,在不删除循环依赖的情况下解决问题的唯一方法是重构其中一个类以使用 setter 注入:

    class A {
        public $b;
    
        public function __construct(B $b) {
            $this->b = $b;
        }
    }
    
    
    class B {
        public $a;
    
        public function setA(A $a) {
            $this->a = $a;
        }
    }
    

    这允许构造对象:

    $b = new B();
    $a = new A($b);
    $b->setA($a);
    

    附上原代码:

    class A {
        public $b;
    
        public function __construct(B $b) {
            $this->b = $b;
        }
    }
    
    class B {
        public $a;
    
        public function __construct(A $a) {
            $this->a = $a;
        }
    }
    

    你不能构造它并遇到与容器相同的问题:

    $b = new B(new A(new B(new A(new B(.............))))
    

    使用诸如 ReflectionClass::newInstanceWithoutConstructor 之类的 hack 让容器解决此问题的问题在于,您的对象现在依赖于使用此方法的创建逻辑。您基本上将代码耦合到容器,这是一个糟糕的设计,因为您的代码现在不再可移植,并且不能在没有容器的情况下使用来执行对象构造。

    【讨论】:

    • 谢谢,汤姆!您写了You essentially couple the code to the container which is a poor design as your code is now no longer portable - 正确的做法是什么?
    • 我已经更新了 Dice 来解决这个问题,但它的设计仍然很糟糕,但它会停止无限循环。
    • 我会修复我的设计!是否违反了 SRP(单一职责原则)?
    • 循环引用本身意味着两个对象共享一个责任,很可能其中一个(或两个)类做的太多了(看看它们的依赖量,这几乎可以肯定是情况,见misko.hevery.com/code-reviewers-guide/flaw-class-does-too-much)。如果他们真的那么相互依赖,他们就是一个单一的责任。
    • 汤姆,非常感谢您的帮助和建议。我终于完全改变了我的模式,所以我的课程现在并没有做太多,而且我也避免了很多递归。我的应用程序加快了速度。非常感谢。 附言我希望有一天你会发布带有 cmets 和 GNU style 格式的 Dice(正如我曾经给你发过电子邮件的那样 - codepad.viper-7.com/yqJ8cl )。最好的问候。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-09-18
    • 2020-03-14
    • 2013-10-21
    • 2018-03-03
    • 1970-01-01
    • 1970-01-01
    • 2010-11-29
    相关资源
    最近更新 更多