管理类依赖性:依赖性注入,服务定位器和工厂简介,第2部分

tech2023-11-04  101

In the previous installment of this two-part series, I went though the development of a few straightforward examples exploring in a fairly approachable fashion a couple of methodologies new to PHP when it comes to handling class dependencies. In this primer, I covered the inclusion of sinful “new” operators in constructors, a method that should be quickly thrown in the trash can with no trace of guiltiness, as well as the use of injected factories.

在一篇文章中这样两部分的系列,我虽然去了几个简单的例子,一个相当平易近人的方式探索了几个新的方法,以PHP的,当涉及到处理类依赖关系的发展。 在本入门文章中,我介绍了构造函数中包含有罪的“新”运算符的方法,该方法应Swift扔入垃圾箱,而无需罪恶感,并使用注入的工厂。

While it’s fair to admit that factories do have a neat niche in a number of special use cases, I’m not so merciless as to condemn Service Locators and plain Dependency Injection to an unfair exile. In this final part we’ll take a closer look at the implementation of these popular patterns so that you can pick up the one that best suits the need at hand.

虽然可以公平地承认工厂在许多特殊用例中确实有一个利基市场,但我并不是毫不残酷地谴责服务定位器和纯属依赖注入到不公平的流放中。 在最后一部分中,我们将仔细研究这些流行模式的实现,以便您可以选择最适合当前需求的模式。

中间人–通过服务定位器获取班级协作者 (The Middle Man – Getting Class Collaborators via a Service Locator)

While a Service Locator is considered in many cases a fancy, mind-blowing approach in the world of PHP, the truth is that the pattern, with some creative slants of course, has enjoyed of a long and venerable life within the language’s domain. At its roots, a Service Locator is nothing but a centralized registry, most of the time static (although dynamic ones are appearing in some popular frameworks), filled with a bunch of objects. Nothing more, nothing less.

尽管在许多情况下,Service Locator在PHP领域中被认为是一种花哨的,令人鼓舞的方法,但事实是,这种模式(当然也带有一些创造性的倾向)在该语言的领域享有悠久而古老的生命。 从根本上讲,服务定位器只不过是一个集中式注册表,在大多数情况下,它是静态的(尽管动态的出现在某些流行的框架中),其中充满了许多对象。 仅此而已。

As usual, one didactical approach to understand what’s going on under the hood of a Service Locator is by example. If we wanted to appeal to the pattern’s virtues for giving the FileStorage object from last time its dependency, the locator could be implemented like this:

像往常一样,通过示例的方法来了解服务定位器背后的情况。 如果我们想利用模式的优点,即从上次开始给FileStorage对象提供依赖关系,则可以按以下方式实现定位器:

<?php namespace LibraryDependencyInjection; interface ServiceLocatorInterface { public function set($name, $service); public function get($name); public function has($name); public function remove($name); public function clear(); } <?php namespace LibraryDependencyInjection; class ServiceLocator implements ServiceLocatorInterface { protected $services = array(); public function set($name, $service) { if (!is_object($service)) { throw new InvalidArgumentException( "Only objects can be registered with the locator."); } if (!in_array($service, $this->services, true)) { $this->services[$name] = $service; } return $this; } public function get($name) { if (!isset($this->services[$name])) { throw new RuntimeException( "The service $name has not been registered with the locator."); } return $this->services[$name]; } public function has($name) { return isset($this->services[$name]); } public function remove($name) { if (isset($this->services[$name])) { unset($this->services[$name]); } return $this; } public function clear() { $this->services = array(); return $this; } }

Take my opinion as a form of catharsis if you want to, but I must confess that I’m rather reluctant to use a service locator over plain dependency injection, even if the locator is dynamic, rather than a static registry plagued with mutable global access issues. In either case, it’s worth looking at and seeing how it can be passed along into the FileStorage class. Here we go:

如果愿意,以我的观点作为宣泄的一种形式,但我必须承认,我很不愿意在纯依赖注入上使用服务定位器,即使该定位器是动态的,也不会因为静态注册表而受到可变全局访问的困扰问题。 无论哪种情况,都值得一看,看看如何将其传递到FileStorage类中。 开始了:

<?php namespace LibraryFile; use LibraryDependencyInjectionServiceLocatorInterface; class FileStorage { const DEFAULT_STORAGE_FILE = "data.dat"; protected $serializer; protected $file; public function __construct(ServiceLocatorInterface $locator, $file = self::DEFAULT_STORAGE_FILE) { $this->setFile($file); $this->serializer = $locator->get("serializer"); } public function setFile($file) { if (!is_file($file)) { throw new InvalidArgumentException( "The file $file does not exist."); } if (!is_readable($file) || !is_writable($file)) { if (!chmod($file, 0644)) { throw new InvalidArgumentException( "The file $file is not readable or writable."); } } $this->file = $file; return $this; } public function read() { try { return $this->serializer->unserialize( @file_get_contents($this->file)); } catch (Exception $e) { throw new Exception($e->getMessage()); } } public function write($data) { try { return file_put_contents($this->file, $this->serializer->serialize($data)); } catch (Exception $e) { throw new Exception($e->getMessage()); } } }

To make things clear, I dropped the FileStorage class again from top to bottom, as this probably makes it easier to understand how its driving logic remains untouched with regards to its read()/write() methods. The constructor is by far the most relevant block as it consumes the locator which is then charged with the responsibility of getting in a serializer object.

为了清楚FileStorage ,我再次将FileStorage类从上到下放置,因为这可能使您更容易理解其驱动逻辑在read() / write()方法方面如何保持不变。 到目前为止,构造函数是最相关的块,因为它消耗了定位器,然后该定位器负责获取串行器对象。

While its implementation is straightforward, this approach is a far cry away from being innocent. First, FileStorage now has a strong dependency with the locator, even when it’s possible to pass around different implementations of it. Second, since the locator is inherently an intermediate provider of the class’ dependency, it infringes on the Law of Demeter at some point as well. This is an unavoidable artifact tied to the roots of the pattern. we should either learn to live with the issue or just forget about the pattern altogether. There’s no middle ground to ponder to here.

尽管它的实现很简单,但与纯真的做法相去甚远。 首先,即使有可能传递其不同的实现, FileStorage现在对定位器也具有很强的依赖性。 其次,由于定位器本质上是该类依赖关系的中间提供者,因此它在某些时候也违反了德米特法则 。 这是与模式根源相关的不可避免的伪影。 我们应该学会忍受这个问题,或者只是完全忘记这种模式。 这里没有中间要考虑的问题。

Here’s the code that shows how to get things finally rolling with the locator:

这是显示如何最终使定位器滚动的代码:

<?php $locator = new ServiceLocator; $locator->set("serializer", new Serializer()); $fileStorage = new FileStorage($locator); $fileStorage->write("This is a sample string."); echo $fileStorage->read();

Although rather primitive, the example shows the locator resembles at some point the structure of a basic Dependency Injection Container (DIC). The main difference is that the locator is usually injected or statically consumed inside the client classes, while a DIC always lives and breaths outside of them.

尽管很原始,但该示例显示了定位器在某种程度上类似于基本的依赖项注入容器(DIC)的结构。 主要区别在于,定位器通常是在客户端类内部注入或静态消耗的,而DIC始终在客户端类外部生活和呼吸。

So far, we’ve covered a decent amount of common approaches used for managing class dependencies. Still, we haven’t swum in the waters of the simplest one of all… yep, the sweet creek of raw Dependency Injection!

到目前为止,我们已经介绍了用于管理类依赖关系的大量常用方法。 尽管如此,我们还没有在所有最简单的水域中畅游……是的,原始依赖注入的甜蜜小溪!

最简单的结局-使用无格式依赖注入 (The Greatest and Simplest Finale – using Plain Dependency Injection)

It might sound obvious, I know, but the most efficient and easiest way to provide FileStorage with a serializer object is with plain ol’ Dependency Injection, thus moving away from any coupling issues or breaking the commandments imposed by the Law of Demeter. Of course, I assume you’re clever enough and already knew that from the very beginning. Even so, it doesn’t hurt to show how the class in question would look when hooked up to this approach:

我知道,这听起来似乎很明显,但是为FileStorage提供序列化程序对象的最有效,最简单的方法是简单的“依赖注入”,从而摆脱了任何耦合问题或违反了Demeter法则所施加的命令。 当然,我认为您足够聪明,并且从一开始就已经知道这一点。 即使这样,显示所涉及的类在采用这种方法时的外观也不会有什么坏处:

<?php namespace LibraryFile; class FileStorage { const DEFAULT_STORAGE_FILE = "data.dat"; protected $serializer; protected $file; public function __construct(Serializable $serializer, $file = self::DEFAULT_STORAGE_FILE) { $this->setFile($file); $this->serializer = $serializer; } // the remaining methods go here } $fileStorage = new FileStorage(new Serializer); $fileStorage->write("This is a sample string."); echo $fileStorage->read();

That’s ridiculously easy to assimilate. In this case, the whole object graph is so anemic that appealing to the nuts and bolts of an external DIC to create it would be just pretty much overkill. On behalf of an instructive cause, though, we could build a primitive container, similar to the swift, lightweight Pimple, and see in a jiffy how to use it for wiring up all the objects that compose the file storage module:

这很容易被吸收。 在这种情况下,整个对象图是如此贫乏,以至于吸引外部DIC的螺母和螺栓来创建它几乎是多余的。 但是,出于指示性的原因,我们可以构建一个原始容器,类似于快速,轻量级的Pimple ,并轻松地了解如何使用它来连接组成文件存储模块的所有对象:

<?php namespace LibraryDependencyInjection; interface ContainerInterface { public function set($name, $service); public function get($name, array $params = array()); public function has($name); public function remove($name); public function clear(); } <?php namespace LibraryDependencyInjection; class Container implements ContainerInterface { protected $services = array(); public function set($name, $service) { if (!is_object($service)) { throw new InvalidArgumentException( "Only objects can be registered with the container."); } if (!in_array($service, $this->services, true)) { $this->services[$name] = $service; } return $this; } public function get($name, array $params = array()) { if (!isset($this->services[$name])) { throw new RuntimeException( "The service $name has not been registered with the container."); } $service = $this->services[$name]; return !$service instanceof Closure ? $service : call_user_func_array($service, $params); } public function has($name) { return isset($this->services[$name]); } public function remove($name) { if (isset($this->services[$name])) { unset($this->services[$name]); } return $this; } public function clear() { $this->services = array(); } }

The similarities between the DIC and the service locator coded before are anything but casual. The former, however, implements a pinch of extra functionality as it’s capable of storing and calling closures on request, something that closely mimics the forces behind Pimple.

DIC和之前编码的服务定位器之间的相似之处绝非偶然。 但是,前者实现了一些额外的功能,因为它能够根据请求存储和调用闭包,这与Pimple背后的力量非常相似。

With this naïve DIC in place, the whole file storage object graph could be assembled on demand:

有了这个简单的DIC,就可以按需组装整个文件存储对象图:

<?php $container = new Container(); $container->set("filestorage", function() { return new FileStorage(new Serializer()); }); $fileStorage = $container->get("filestorage"); $fileStorage->write("This is a sample string."); echo $fileStorage->read();

It’s clear that the DIC is (or in theory it should be) an element stepping beyond the boundaries of the client classes, which are completely agnostic about its rather furtive and silent existence. This form of unawareness is quite possibly one of the biggest differences that exist between a DIC and a service locator, even though it’s possible to inject a DIC into other classes as well through a sort of “recursive” injection.

显然,DIC是(或从理论上讲应该是)超越客户端类边界的元素,而客户端类完全不了解其隐性和隐性存在。 这种不了解的形式很可能是DIC和服务定位器之间存在的最大差异之一,即使也可以通过某种“递归”注入将DIC注入其他类中。

In my opinion, this process not only unnecessarily degrades the DIC to the level of a plain service locator, but corrupts its natural “outsider” condition. As a rule of thumb, regardless of if you’re using a DIC or a service locator, make sure the elements will be playing the role they’re supposed to play without stepping on each other toes.

我认为,此过程不仅不必要地将DIC降级为普通服务定位器的级别,而且会破坏其自然的“局外人”条件。 根据经验,无论您使用的是DIC还是服务定位器,请确保这些元素将发挥它们应发挥的作用而不会互相踩到脚趾。

结束语 (Closing Remarks)

It seems that the old bad days when managing class dependencies was just a matter of dumping a few “new” operators into fat, bloated constructors are finally fading away. By contrast, an emerging combination of patterns, with Dependency Injection leading the charge, are strongly pushing through to every corner of PHP, a paradigm shift that has already had a beneficial impact on the quality of several existing codebases out there.

似乎过去管理类依赖关系的糟糕时光只是将一些“新”运算符倾倒在胖子中的问题,constructor肿的构造函数终于消失了。 相比之下,由Dependency Injection主导的新兴模式组合正在大力推向PHP的每个角落,这种范式转换已经对现有的几种代码库的质量产生了有益的影响。

Still, the big question keeps floating around in circles: DICs, service locators, injected factories… which ultimately fits the bill the best? As I said before, making the right decision largely depends on what you’re dealing with in the first place. In all cases, they’re just variants of Inversion of Control, decorated with some nice refinements and fancy mixtures. And you know that IoC is the way to go with polymorphism, hence with testability. Let your personal needs be your most confident advisors; they won’t disappoint you.

但是,仍然存在一个大问题:DIC,服务定位器,注入工厂……最终最适合该法案吗? 正如我之前说过的那样,做出正确的决定很大程度上取决于您首先要处理的内容。 在所有情况下,它们都是控制反转的变体,带有一些不错的改进和精美的混合物。 而且您知道IoC是实现多态性和可测试性的方法。 让您的个人需求成为您最有信心的顾问; 他们不会让你失望的。

Image via SvetlanaR / Shutterstock

图片来自SvetlanaR / Shutterstock

翻译自: https://www.sitepoint.com/managing-class-dependencies-2/

最新回复(0)