类继承和依赖注入的关系_管理类依赖关系:依赖关系注入,服务定位符和工厂简介,第1部分...

类继承和依赖注入的关系

Let’s face it: for good or bad, OOP has been actively drilling deep holes in the soil of PHP in the last few years, hence its ubiquitous presence now is anything but breaking news. Furthermore, while this steady incremental “objectification” in the language’s terrain is generally considered a beneficial shift towards new and more promising horizons, for those still rooted in the procedural paradigm, the process has been far from innocuous.

让我们面对现实吧:无论好坏,OOP在过去的几年中一直在PHP的土壤中积极钻探深Kong,因此,它无处不在的存在现在绝不是什么新闻。 此外,尽管通常认为在语言领域中这种不断增加的“客观化”是向新的和更有希望的视野的有益转变,但对于仍然植根于程序范式的人们来说,这一过程并非无害。

There’s a few solid arguments that sustain this mindset: first and foremost the nature of OOP is awkward and inherently complex, plagued with nuance that can take literally years to tame. Also, defining the APIs that OO components use for interacting with one another can be quite a burden at times, especially when it comes to designing systems whose backbone rests on facilities provided by large and tangled object graphs.

有一些可靠的论据支持这种思维方式:首先,最重要的是,OOP的本质是笨拙的,内在的复杂,充满细微差别,可能需要花费数年才能驯服。 而且,有时定义OO组件用于彼此交互的API可能会很繁重,尤其是在设计其主干基于大型且复杂的对象图提供的功能的系统时

From a programmer’s perspective, the process of designing easily consumable APIs while still keeping a decent level of decoupling between the involved classes is always a tricky trade-off, as the more expressive the dependencies are, the harder the work is that needs to be done behind the scenes for wiring up the collaborators in their proper sequence. This fact itself is a dilemma that has plagued the minds of developers from long ago, which is certainly harder to digest as time goes by. With applications becoming increasingly bloated, housing inside their boundaries a growing number of classes, the construction of clean, declarative APIs isn’t just a loose practice anymore that can eventually be pushed into the language’s mainstream from time to time. It’s simply a must.

从程序员的角度来看,在设计易于使用的API的同时仍保持相关类之间的良好去耦水平的过程始终是一个棘手的权衡,因为依赖项越富表现力,工作就越需要完成在幕后,以适当的顺序连接协作者。 这个事实本身就是一个困扰很久以前的开发人员思想的困境,随着时间的流逝,这无疑更加难以消化。 随着应用程序变得越来越膨胀,在其边界内容纳越来越多的类,构造清晰的声明式API不再只是一种宽松的做法,最终最终会不时地被推入该语言的主流。 这是必须的。

This raises a few interesting questions: how should a class be provided with its dependencies without cluttering its API, or even worse, without coupling it to the dependencies themselves? Just by appealing to the benefits of Dependency Injection? Through the reigns of an injected Service Locator?

这就提出了一些有趣的问题:如何为类提供其依赖项而又不会使其API混乱,甚至更糟,而又不将其与依赖项本身耦合? 只是通过吸引依赖注入的好处? 通过注入的服务定位器的统治?

For obvious reasons, there’s no a one size fits all solution to the problem, even when all of the aforementioned approaches have something in common. Yes, to some extent they all make use of some form of Inversion of Control, which not only allows you to decouple the dependencies from the class but also keeps a neat level of insulation between the dependences’ interfaces and the implementation, making mocking/testing a breeze.

出于明显的原因,即使上述所有方法都有共同点,也没有一种方法可以完全解决问题。 是的,它们在某种程度上都利用了某种形式的控制反转,这不仅使您可以将依赖项与类解耦,还可以使依赖项的接口与实现之间保持整洁的隔离,从而进行模拟/测试微风。

Evidently, the topic is a world away from being banal, and as such it deserves a close, in-depth analysis. In this two-part series I’ll be doing a quick roundup of some of the most common methodologies that can be employed for managing class dependencies in modern application development, ranging from using service locators and injectable factories, to sinking your teeth into plain vanilla Dependency Injection, and even implementing a naive Dependency Injection Container.

显然,该主题是一个远离平庸的世界,因此,它值得进行深入,深入的分析。 在这个由两部分组成的系列文章中,我将快速汇总一些可用于管理现代应用程序开发中的类依赖关系的最常用方法,从使用服务定位器和可注入工厂,到将您的牙齿陷入普通的香草依赖注入,甚至实现了幼稚的依赖注入容器。

(希望)消失的瘟疫–构造函数中的“新”运算符 (A (Hopefully) Vanishing Plague – “new” Operators in Constructors)

The mantra is somewhat old, sure, but its claim still rings loud and clear: “placing ‘new’ operators in constructors is just plain evil.” We all know that now, and even take for granted that we’ll never commit such a cardinal sin. But this was literally the default method used for years for a class to look up its dependencies before Dependency Injection reached the PHP world. For the sake of completeness, let’s recreate this clunky, old-fashioned scenario and remind ourselves why we should get away from such a harmful plague.

这种口头禅肯定有些陈旧,但是它的主张仍然响亮而清晰:“在构造函数中放置'new'运算符简直就是邪恶。” 我们现在都知道,甚至理所当然地认为,我们绝不会犯下这种根本性的罪行。 但这实际上是多年来使用的默认方法,用于类在Dependency Injection进入PHP世界之前查找其依赖项。 为了完整起见,让我们重新创建这种笨拙的老式场景,并提醒自己为什么要摆脱这种有害的瘟疫。

Say that we need to build a simple file storage module which must internally consume a serializer in order to read and write data to a specific file. The implementation of the serializer would look something like to this:

假设我们需要构建一个简单的文件存储模块,该模块必须在内部使用一个序列化程序才能将数据读写到特定文件中。 序列化器的实现如下所示:

<?php
namespace LibraryEncoder;

class Serializer implements Serializable
{
    public function serialize($data) {
        if (is_resource($data)) {
            throw new InvalidArgumentException(
                "PHP resources are not serializable.");
        }
        if (($data = serialize($data)) === false) {
            throw new RuntimeException(
                "Unable to serialize the supplied data.");
        }
        return $data;
    }
    
    public function unserialize($data) {
        if (!is_string($data)|| empty($data)) {
            throw new InvalidArgumentException(
                "The data to be unserialized must be a non-empty string.");
        }
        if (($data = @unserialize($data)) === false) {
            throw new RuntimeException("Unable to unserialize the supplied data."); 
        }
        return $data;
    }
}

The Serializer class is just a lightweight implementation of the native Serializable interface and exposes the typical serialize/unserialize duet to the outside world. With this contrived strategy class doing its thing as expected, the aforementioned file storage module could be sloppily coded as follows:

Serializer类只是本机Serializable接口的轻量级实现,将典型的序列化/非序列化二重奏向外界公开。 通过这种精心设计的策略类按预期方式运行,上述文件存储模块可以按如下所示进行草率编码:

<?php
namespace LibraryFile;
use LibraryEncoderSerializer;

class FileStorage
{
    const DEFAULT_STORAGE_FILE = "data.dat";
    protected $serializer;
    protected $file;
    
    public function __construct($file = self::DEFAULT_STORAGE_FILE) {
        $this->setFile($file);
        $this->serializer = new 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());
        }
    }
}

The behavior of FileStorage boils down to just saving and pulling in data from a predefined target file. But the seemingly benign nature of the class is nothing but an illusion, as it blatantly instantiates the serializer in its constructor!

FileStorage的行为可以归结为只是从预定义的目标文件中保存和提取数据。 但是类的看似良性本质不过是一种幻想,因为它在其构造函数中公然实例化了序列化器!

This form of “dictatorial” dependency lookup not only introduces a strong coupling between the class and its dependency, but it condemns testability to a quick death. The following code shows how the artifacts ripple through to client code when using this approach:

这种“独裁”依赖查找的形式不仅在类及其依赖之间引入了强大的耦合,而且将可测试性谴责为快速死亡。 以下代码显示了使用这种方法时工件如何传递到客户端代码:

<?php
$fileStorage = new FileStorage();
$fileStorage->write("This is a sample string.");
echo $fileStorage->read();

Feel free to run the code and it’ll work like a charm, that’s for sure. But we know the coupling is there just beneath the surface, not to mention the fact that there’s not a single clue about if the FileStorage has an internal dependency on another component, or how this one was acquired. Definitively, this is pretty much like an antipattern which goes against the grain when it comes to defining clean, expressive class APIs.

可以随意运行代码,它会像魅力一样工作,这是肯定的。 但是我们知道耦合只是存在于表面之下,更不用说一个事实,即FileStorage是否对另一个组件具有内部依赖性,或者该组件是如何获得的。 绝对地,这很像是反模式,在定义干净的,具有表现力的类API时违反了规则。

There’re a few additional approaches that are worth looking into that can improve the way the two previous classes interoperate with each other. For instance, we could be a bit bolder and inject a factory straight into the internals of FileStorage and let it create a serializer object when necessary, thus making the class’ dependency a little more explicit.

还有一些值得研究的其他方法可以改善前两个类之间的互操作方式。 例如,我们可以大胆一些,直接将工厂注入FileStorage的内部,并在必要时让它创建一个序列化器对象,从而使类的依赖关系更加明确。

延迟通过注入工厂创建依赖项 (Deferring the Creation of Dependencies through an Injected Factory)

Factories, in any of their forms, are nifty elements that allow us to nicely distill object construction from application logic. While such ability is quite remarkable in itself, it can be taken a bit further when mixed with the goodness of Dependency Injection.

任何形式的工厂都是极好的元素,它们使我们能够从应用程序逻辑中很好地提取对象构造。 虽然这种能力本身非常出色,但是与依赖注入的优点混合使用时,可以进一步提高。

If you’re the curious sort like I am, you’ll be wondering how to exploit the benefits provided by a factory for enhancing how the earlier FileStorage class looks up its collaborator, getting rid of the infamous “new” operator in its constructor. At a very basic level, the lookup process could be reformulated through the following factory class:

如果您像我一样好奇,那么您会想知道如何利用工厂提供的好处来增强早期的FileStorage类如何查找其协作者,从而摆脱其构造函数中臭名昭著的“ new”运算符。 从根本上讲,可以通过以下工厂类重新构造查找过程:

<?php
namespace LibraryDependencyInjection;

interface SerializerFactoryInterface
{
    public function getSerializer();
}
<?php
namespace LibraryDependencyInjection;
use LibraryEncoderSerializer;

class SerializerFactory implements SerializerFactoryInterface
{   
    public function getSerializer() [
        static $serializer;
        if ($serializer === null) {
            $serializer = new Serializer;
        }
        return $serializer;
    }
}

Having at hand a dynamic factory charged with the task of creating the serializer on demand, now the FileStorage class can be refactored to accept a factory implementer:

拥有一个动态工厂负责按需创建序列化器的任务之后,现在可以重构FileStorage类以接受工厂实现者:

<?php
namespace LibraryFile;
use LibraryDependencyInjectionSerializerFactoryInterface;

class FileStorage
{
    const DEFAULT_STORAGE_FILE = "data.dat";
    protected $factory;
    protected $file;
    
    public function __construct(SerializerFactoryInterface $factory, $file = self::DEFAULT_STORAGE_FILE) {
        $this->setFile($file);
        $this->factory = $factory;
    }
    
    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->factory->getSerializer()->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->factory->getSerializer()->serialize($data));
        }
        catch (Exception $e) {
            throw new Exception($e->getMessage());
        }
    }
}

The functionality of FileStorage remains pretty much the same, but the tight coupling with the serializer has been broken by injecting an implementer of the SerializerFactoryInterface. This simple twist turns the class into a flexible and testable creature, which exposes a more expressive API, as is now easier to see from the outer world what dependencies it needs. The code below shows the result of these enhancements from the perspective of the client code:

FileStorage的功能几乎相同,但是通过注入SerializerFactoryInterface的实现程序,与SerializerFactoryInterface的紧密耦合已被打破。 这种简单的转变将类变成了一个灵活且可测试的生物,它公开了更具表现力的API,现在可以从外部世界更容易地看出它需要什么依赖。 下面的代码从客户端代码的角度显示了这些增强的结果:

<?php
$fileStorage = new FileStorage(new SerializerFactory());
$fileStorage->write("This is a sample string.");
echo $fileStorage->read();

Let’s not fall into blind, pointless optimism, though. It’s fair to say the refactored implementation of the storage module looks more appealing; this approach makes it trivial to switch out different factory implementations at runtime. But in my opinion, at least in this case, it’s a masked violation of the Law of Demeter since the injected factory acts like an unnecessary mediator to the module’s real collaborator.

但是,我们不要陷入盲目,毫无意义的乐观之中。 可以公平地说,重构的存储模块实现看起来更具吸引力。 这种方法使得在运行时切换出不同的工厂实现变得很简单。 但是我认为,至少在这种情况下,这是对戴米特律法的掩盖违反,因为注入的工厂对于模块的实际协作者而言就像是不必要的调停者。

This doesn’t mean that injected factories are a bad thing at all. But in most cases they should be used only for creating dependencies on demand, especially when the dependencies’ life cycles are shorter than that of the client class using them.

这并不意味着注入工厂根本不是一件坏事。 但是在大多数情况下,它们仅应用于创建按需的依赖关系,尤其是当依赖关系的生命周期短于使用它们的客户端类的生命周期时。

结束语 (Closing Remarks)

Quite often unfairly overlooked in favor of more “seductive” topics, managing class dependencies is unquestionably a central point of object-oriented design and whose underlying logic certainly goes much deeper than dropping a few “new” operators in factories, or worse, in stingy constructors that hide those dependencies from the outside world so they can’t be properly decoupled at will or tested in isolation.

人们常常不偏不倚地忽略了更多的“诱人”主题,而管理类依赖无疑是面向对象设计的中心点,其基本逻辑肯定比在工厂中丢掉一些“新”运算符要深得多,或者更糟的是,构造函数将那些依赖项隐藏在外部世界中,这样就不能随意地对其进行解耦或孤立地对其进行测试。

There’s no need to feel that all is lost, however, There exists a few additional approaches that can be utilized for handling in an efficient manner the way that classes are provided with their collaborators, including the use of the Service Locator pattern, raw Dependency Injection, and even appealing to the benefits of a Dependency Injection container. All these ones will be covered in detail in the follow up, so stay tuned!

不必感到一切都丢失了,但是,可以使用一些其他方法来有效地处理与合作者一起提供类的方式,包括使用服务定位器模式,原始依赖注入,甚至吸引了依赖注入容器的好处。 所有这些都将在后续内容中详细介绍,请继续关注!

Image via SvetlanaR / Shutterstock

图片来自SvetlanaR / Shutterstock

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

类继承和依赖注入的关系

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值