哈,DI下的服务定位器是多么优美的伪装。 甚至看起来像DI! :-)
这是我以前的出版物Dependency Injection,JavaScript和ES6 Modules的第一个评论。 感谢同事symbix'u的评论tk。 正是他成为了沉浸在彼此之间差异微妙之处的原因。 在标题下,我删减了对问题的回答。

(KDPV没有多大意义,主要用于视觉识别该出版物等)
首先,我们将处理这些定义( 我将在PHP中提供一些代码示例,在JS中提供一些代码示例,因为这两种语言目前都在我的书包中,而其他一些则是因为我从互联网上窃取了这些示例 )。
依赖注入
简而言之,DI-而不是require / import模块,而是通过构造函数参数(或属性设置器)注入依赖项。 也就是说,这个大词的背后是简单的“通过构造函数参数传递类依赖关系”。
Risedphantom的同事的这一评论相当准确地传达了这一现象的实质。 为了促进开发人员对代码的理解,所有依赖项都以构造函数参数的形式(通常但并非总是如此)进行了明确描述:
class MainClass { public function __construct($dep1, $dep2, $dep3) {} }
仅此而已。 DI-他就是这样。 我们需要的依赖关系由那里的某个人提供。 这个“有人在那里”将他们带到哪里-DI不在乎。
在分析代码时,重要的是要了解什么真正是它的“内部”(以及我们可以安全更改的内容),以及超出/超出此代码职责范围的内容。 这就是激发DI的原因。 为什么依赖性主要通过构造函数传递,而不是通过setter传递? 因为这对开发人员来说更加方便-他立即在一个地方看到了所分析代码的所有依赖关系。 我们习惯于认为DI是类级别的东西,但是函数/方法参数也是DI:
function f(dep1, dep2, dep3) {}
构造器就是这样一种特殊的方法。
服务定位器
服务定位器是一种著名的反模式。 他是做什么的? 提供对一个对象与其他对象的访问。 这是此类定位器的典型界面:
interface ServiceLocator { public function addInstance(string $class, Service $service); public function addClass(string $class, array $params); public function has(string $interface): bool; public function get(string $class): Service; }
定位器是一个容器,您可以在其中放置现成的对象(或设置其创建规则),然后访问这些对象。 对于定位器而言,对象在其中的显示方式基本上没有关系-它们是从外部显式创建的,并放置在容器中,或者由容器本身根据给定的规则创建的。
服务定位器负责存储对象并提供对它们的访问。 仅此而已。
依赖注入容器
什么是DI容器? 根据对“ 依赖注入原理,实践和模式 ”内容的自由介绍,该 软件库提供了DI功能并允许自动执行对象组成,拦截和生命周期管理中涉及的许多任务。DI容器也称为Inversion of控制(IoC)容器。(第3.2.2节)“
也就是说,DI容器主要负责创建对象,但仅在第二个对象中负责对象的存储。 如果应用程序本身不需要整个应用程序通用的对象(例如配置或记录器),那么DI容器甚至可能根本不存储任何创建的对象。
例如,这里是Symfony DI容器接口:
interface ContainerInterface extends PsrContainerInterface { public function set(string $id, ?object $service); public function get($id, int $invalidBehavior = self::EXCEPTION_ON_INVALID_REFERENCE); public function has($id); public function initialized(string $id); public function getParameter(string $name); public function hasParameter(string $name); public function setParameter(string $name, $value); }
如有必要,您可以轻松地说服自己,DI容器的界面与Locator Services的界面非常相似-相同的get
, has
和set
/ add
。
为什么服务定位器不好?
但是什么都没有。 模板本身不是很糟糕,但是有时会被使用。 特别是,有一种观点认为:“ 服务定位器违反了静态类型语言中的封装,因为此模式不能明确表达先决条件 。” 还有一个示例违规:
public class OrderProcessor : IOrderProcessor { public void Process(Order order) { var validator = Locator.Resolve<IOrderValidator>(); if (validator.Validate(order)) { var shipper = Locator.Resolve<IOrderShipper>(); shipper.Ship(order); } } }
就像,那太糟糕了,但是不错-像这样:
public class OrderProcessor : IOrderProcessor { public OrderProcessor(IOrderValidator validator, IOrderShipper shipper) public void Process(Order order) }
嘿,我们只是IOrderValidator
了使用IOrderValidator
和IOrderShipper
创建对象本身的IOrderValidator
! 在此应用程序的其他地方可能有类似以下代码的内容:
var validator = Locator.Resolve<IOrderValidator>(); var shipper = Locator.Resolve<IOrderShipper>(); var processor = new OrderProcessor(validator, shipper); processor.Process(order);
成分根
说到依赖注入,我们不禁要提出一个概念,例如“ 合成根” (在下文中,我将其称为“集合点”)。 这是与创建依赖关系及其实现的职责委派给的同一个人 。
在组装点,所有依赖项都被明确定义,相应的对象在等待的地方创建和实现。 在这里,服务定位器和DI容器之间的差异很小。 两者都允许创建新对象,存储创建的对象,提取存储的对象。 我什至承诺要断言,在这个地方,它们之间没有根本的区别。
主要区别
DI容器和Services Locator的容器之间最明显的区别在哪里? 在其他任何地方,尤其是在静态访问容器的情况下:
$obj = Container::getInstance()->get($objId);
来了 以这种形式,在任何地方(除非它是一个Assembly Point,但是在那里使用静态访问都是非常可疑的),任何容器都将成为称为Service Locator的反模式。
一位VolCh同事简短而简洁地回答了我的问题 :
您认为真正的DI与伪装成DI的服务定位器有何不同?
像这样:
集装箱出入
实际上,整个出版物只是他的回答在这里的更详细的部署。
合法使用容器
因此,容器是DI容器还是服务定位器的容器在很大程度上取决于我们在何处以及如何使用它。 正如我在上面所述,在组装点,容器类型之间的差异消失了。 但是,如果容器本身是任何类的依赖项,我们该怎么办?
class Foo { public function __construct(Container $container){} }
同样,这完全取决于我们如何使用容器。 这是两个示例,其中一个与服务定位器的容器完全匹配并且是反模式,而另一个在某些条件下拥有生命权。
class Foo { private $dep; public function __construct(Container $container) { $this->dep = $container->get('depId'); } }
class Foo { private $container; public function __construct(Container $container) { $this->container = $container; } public function initModules($modules) { foreach ($modules as $initClassId) { $init= $this->container->get($initClassId); $init->exec(); } } }
总结
实际上,所有事情都有些复杂,DI容器应该比Services Locator具有更多的功能,但是此出版物的本质是即使最复杂的DI容器也可以轻松用作Services Locator,而要使用Services Locator作为DI容器-您需要尝试。
聚苯乙烯
我不禁提供Sergei Teplyakov的编程博客的链接- 这些问题在这里很好地介绍了。