依赖注入容器(DIC) - 如何处理过时的服务?

时间:2014-06-12 11:15:16

标签: symfony dependency-injection

因此,Drupal使用基于Symfony2的依赖注入容器(DIC)来组织其服务。

此外,我喜欢自己使用这种模式(使用更简单的手工解决方案)来进行小型项目。

简化,看起来像这样:

class Container {
  private $services = array();
  function getService($key) {
    if (isset($this->services[$key])) {
      return $this->services[$key];
    }
    $method = 'create_' . $key;
    // @todo Check if method exists.
    // Call the method to lazy-create the service.
    return $this->services[$key] = $this->$method($key);
  }

  function create_kitchen() {
    // Kitchen depends on Stove.
    $stove = $this->getService('stove');
    return new Kitchen($stove);
  }

  function create_stove() {
    return new Stove();
  }
}

$container = new Container();
$kitchen = $container->getService('kitchen');

到目前为止一切顺利 但是,如果我想更换一个新的炉子而不更换厨房怎么办?

$kitchen = $container->getService('kitchen');
$kitchen->cookAnEgg();
$container->replace('stove', new BetterStove());
$kitchen->cookAnEgg();

我需要一种机制来替换厨房,让旧的厨房实例变得过时,或者我需要让厨房知道炉子已经更换,所以第二个鸡蛋可以用新炉子烹饪。

如果厨房要自己更换炉子怎么办?

class Kitchen {
  private $stove;
  private $electrician;
  function __construct(Stove $stove, Electrician $electrician) {
    $this->stove = $stove;
    $this->electrician = $electrician;
  }
  function cookAnEgg() {
    while ($this->stove->isBroken()) {
      $this->electrician->installNewStove();
    }
    ..
  }
}

厨房如何了解新炉灶?

有没有最佳做法来处理这种情况?

我会考虑使用观察者模式,但与DIC结合使用的最佳做法是什么?

编辑:
我将它作为Symfony2进行分类,但我认为它可以被视为适用于各种依赖注入容器的更一般的问题。

编辑II:
扩展了这个例子。

2 个答案:

答案 0 :(得分:1)

在我看来,依赖注入的重点在于您可以在创建服务定义之前决定哪个炉子最好,而使用它的代码完全不知道炉子的工作方式。

这使您可以将服务实现与调用代码分离。 (理论上,因为您仍然必须知道服务对象的接口,DIC无助于我们隐藏它们。)

然后你可以嘲笑一个炉子并将其放入容器中进行测试,而不会产生生产炉可能存在的依赖性。

如果您在流中切换服务,则不再注入依赖项,您只是将容器用于其他模式。

基本上:不要这样做。 :-)找到导致需要不同炉子的配置,并在*.services.yml或编译通道中处理该依赖,和/或在不改变其界面的情况下更好地使炉子工作。

答案 1 :(得分:0)

这是由一些Creational design pattern:工厂或构建器处理的,例如,您将所有参数传递给它,以便决定它应返回哪种服务类型以及如何构造服务对象图。

Symfony2已经实现了这一切。我没有看到自己这样做的意义。您可以使用DPI组件,而不是使用整个symfony2堆栈。

但即使这对你来说太大了,你也可以选择Pimple

在很多场合学习的过程中自己写作可能会很有趣,但有时它只是重新发明轮子。

修改

Symfony容器不是以某种方式制作的,因此可以在生产运行时修改它,因此它不支持$container->replace('stove', new BetterStove());。这也是运行时的问题。在构建时,它是不同的,一旦定义了服务,就可以重新定义,但我知道你在询问prod运行时。因此,一旦容器完成(构建),就不会再对其进行任何更改。

因此,在您的示例中,您有Stove()Kitchen(Stove)。一段时间后,您添加BetterStove : Stove,并保持旧的Stove()Kitchen(Stove)。答案是,现在您将拥有两个厨房:一个使用旧Stove构建,另一个使用新BetterStove构建。

简单,直接的解决方案是添加kitchen_with_better_stove服务,并让容器的用户决定何时需要普通厨房,以及何时需要新的更好的炉灶。

如果做出决定的逻辑,需要哪种厨房,并不复杂,而且你不想在你的解决方案中重复一遍,那么你可以将它封装成一个工厂方法,其中包含制作这种方法所需的所有参数一个决定,然后容器的用户将查询工厂,并调用它的getKitchen(arg1,arg2 ...)女巫将返回Kitchen旧的常规Stove和新的BetterStove,取决于你的那些参数传递给工厂方法。

Ofc,在一个简单的手工制作的解决方案中,你可以让可变的“容器”做各种各样的东西,但至少我理解DYC,这不是重点 - 容器只是组装你的复合材料,而不是逻辑对他们来说。