ioc - 控制反转_控制反转-好莱坞原则

ioc - 控制反转

There’s a consensus among programmers (myself included, so here’s my own public mea culpa) that Inversion of Control (IoC) is nothing more than a synonym for plain old Dependency Injection (DI). There’s a pretty intuitive reason that sustains this mindset: if the motivation behind DI is to promote the design of classes whose external collaborators are supplied by its surrounding context rather than inversely looking up them, the process can effectively been seen as a form of IoC.

有程序员之间(包括我自己,所以这是我自己的公共过失 ),其控制反转(IOC),无非是为普通的旧依赖注入(DI)的同义词共识。 有一个很直观的理由支持这种思维方式:如果DI背后的动机是促进类的设计,而这些类的外部协作者是由周围的环境提供的,而不是反向查找它们,则该过程可以有效地视为IoC的一种形式。

But while the DI = IoC equation can be considered generally valid, the concept of inversion of control itself is actually much broader. In fact, it could be said that DI is a specific use case which exploits the benefits of IoC, but it’s far from being the only one. This leads us back to the start; if DI is just a pattern that relies on the strengths of IoC, what’s IoC really then?

但是,虽然可以认为DI = IoC方程通常是有效的,但控制反演本身的概念实际上更为广泛。 实际上,可以说DI是一种利用IoC优势的特定用例,但它远非唯一的。 这使我们回到起点。 如果DI只是依靠IoC优势的模式,那么IoC到底是什么?

Traditionally, application components have been designed to operate on and control the execution environment, an approach that delivers well to some extent. For instance, a logging module could be implemented to log data to a file, and how and when to log the data would be a entirely under control of the module. The log file (a part of the environment in this case) would be just an external, passive element with no influence on the way the module works. But let’s say we need to extend the module’s functionality and give it the ability for additionally logging data to a database, or eventually even via email. Upgrading the module to expose the extra functionality will make it grow in complexity, becoming more bloated as the logic required to attend to these additional duties is packaged behind the same API. The approach works, but won’t scale at all.

传统上,应用程序组件被设计为在执行环境上运行和控制执行环境,这种方法在某种程度上可以很好地实现。 例如,可以实现日志记录模块以将数据记录到文件,并且如何以及何时记录数据将完全在模块的控制之下。 日志文件(在这种情况下为环境的一部分)只是一个外部被动元素,不会影响模块的工作方式。 但是,假设我们需要扩展模块的功能,并使其具有将数据额外记录到数据库,甚至最终通过电子邮件记录的功能。 升级模块以显示额外的功能将使其复杂度增加,因为承担这些额外职责所需的逻辑被封装在同一API中,因此变得更加肿。 该方法有效,但根本无法扩展。

This tangled situation can be sorted out in a fairly simple manner. Instead of making the module completely responsible for logging data to multiple endpoints, we can transfer the responsibility straight to the external environment. The module’s implementation would remain ridiculously simple, limited to acting as a simple event dispatcher. On the flip side, the environment would be responsible for implementing all of the logic required to log data entirely independent from the module in question.

这种纠结的情况可以用相当简单的方式解决。 无需使模块完全负责将数据记录到多个端点,我们可以将职责直接转移到外部环境。 该模块的实现将非常简单,仅限于充当简单的事件分发程序。 另一方面,环境将负责实现完全独立于所讨论模块的数据记录所需的所有逻辑。

Not surprisingly, the process of inverting these responsibilities between components and the environment is formally known as Inversion of Control (or in a more relaxed jargon, The Hollywood Principle), and its implementation can be a real boost when it comes to developing extensible, highly-decoupled program modules.

毫不奇怪,在组件和环境之间转换这些职责的过程被正式称为“控制反转”(或者更轻松的术语, 《好莱坞原理》 ),并且在开发可扩展的,高度可扩展的控件时,其实现将是真正的推动力。解耦的程序模块。

Of course, IoC is a language-agnostic paradigm, and as such it’s possible to consume it in the PHP world without much fuss.

当然,IoC是一种与语言无关的范例,因此可以在PHP世界中使用它而不必大惊小怪。

实现控制反转–观察域对象 (Achieving Inversion of Control – Observing Domain Objects)

IoC has indeed an ubiquitous presence, so it’s pretty easy to find implementations of it production. The first use case that comes to mind is Dependency Injection, but there are many other cases equally demonstrative, especially when stepping on the terrain of Event-Driven Design. If you’re wondering in what parallel universe IoC gets along with event handling mechanisms, consider a classic in the GoF repertoire: the Observer pattern.

IoC确实存在无处不在,因此很容易找到其生产的实现。 我想到的第一个用例是“依赖注入”,但还有许多其他案例同样具有示范性,尤其是在踏入事件驱动设计领域时。 如果您想知道IoC与事件处理机制结合使用的是哪种并行Universe,请考虑GoF曲目中的经典作品:观察者模式。

Used nearly everywhere, even client-side via JavaScript, observers are a shining example of the IoC concept in action; there’s a highly-decoupled subject focused on doing just a few narrow tasks without polluting the surrounding context while one or more external observers are responsible for implementing the logic required for handling the events triggered by the subject. How to handle the events, and even processing new ones, is entirely a responsibility of the observers rather than the subject.

观察者几乎是无处不在的,甚至是通过JavaScript在客户端使用的,观察者都是IoC概念付诸实践的光辉典范。 有一个高度分离的主题,专注于仅执行几个狭窄的任务而不会污染周围的环境,而一个或多个外部观察者负责实现处理该主题触发的事件所需的逻辑。 如何处理事件,甚至处理新事件,完全是观察者的责任,而不是主题。

An example might be a nice way to make my previous babbling a little bit clearer. So, let’s say we’ve implemented a primitive Domain Model which defines a one-to-many relationship between blog posts and comments. In this case we’ll be deliberately ambitious and give the model the ability for firing off an email to notify the system administrator when a new comment is added to a post.

一个例子可能是使我以前的胡言乱语更加清晰的好方法。 假设我们已经实现了一个原始的域模型,该模型定义了博客文章和评论之间的一对多关系。 在这种情况下,我们将刻意雄心勃勃,并为模型提供能够在将新评论添加到帖子时触发电子邮件以通知系统管理员的功能。

Honestly, implementing such a feature without appealing to IoC would literally be a tangled mess, as we’d be asking the domain objects to doing something that’s way outside their scope. Instead, we could take an IoC-based approach and define the domain classes as follows:

老实说,在不吸引IoC的情况下实现这种功能实际上是一团糟,因为我们要让域对象做超出其范围的事情。 相反,我们可以采用基于IoC的方法,并如下定义域类:

<?php
namespace Model;

interface PostInterface
{
    public function setTitle($title);
    public function getTitle();
    
    public function setContent($content);
    public function getContent();
    
    public function setComment(CommentInterface $comment);
    public function getComments();
}
<?php
namespace Model;

class Post implements PostInterface, SplSubject
{
    private $title;
    private $content;
    private $comments  = [];
    private $observers = [];

    public function __construct($title, $content) {
        $this->setTitle($title);
        $this->setContent($content);
    }
    
    public function setTitle($title) {
        if (!is_string($title) 
            || strlen($title) < 2
            || strlen($title) > 100) {
            throw new InvalidArgumentException(
                "The post title is invalid.");
        }
        $this->title = $title;
        return $this;
    }

    public function getTitle() {
        return $this->title;
    }
    
    public function setContent($content) {
        if (!is_string($content) || strlen($content) < 10) {
            throw new InvalidArgumentException(
                "The post content is invalid.");
        }
        $this->content = $content;
        return $this;
    }
    
    public function getContent() {
        return $this->content;
    }

    public function setComment(CommentInterface $comment) {
        $this->comments[] = $comment;
        $this->notify();
    }
    
    public function getComments() {
        return $this->comments;
    }

    public function attach(SplObserver $observer) { 
        $id = spl_object_hash($observer);
        if (!isset($this->observers[$id])) {
            $this->observers[$id] = $observer;
        }
        return $this;
    }
    
    public function detach(SplObserver $observer) {
        $id = spl_object_hash($observer);
        if (!isset($this->observers[$id])) {
            throw new RuntimeException(
                "Unable to detach the requested observer.");
        }
        unset($this->observers[$id]); 
        return $this;
    }
    
    public function notify() {
        foreach ($this->observers as $observer) {    
            $observer->update($this);
        }
    }
}
<?php
namespace Model;

interface CommentInterface
{
    public function setContent($content);
    public function getContent();
    
    public function setAuthor($author);
    public function getAuthor();
}
<?php
namespace Model;

class Comment implements CommentInterface
{
    private $content;
    private $author;
    
    public function __construct($content, $author) {
       $this->setContent($content);
       $this->setAuthor($author);
    }
    
    public function setContent($content) {
        if (!is_string($content) || strlen($content) < 10) {
            throw new InvalidArgumentException(
                "The comment is invalid.");
        }
        $this->content = $content;
        return $this;
    }
    
    public function getContent() {
        return $this->content;
    }
    
    public function setAuthor($author) {
        if (!is_string($author) 
            || strlen($author) < 2
            || strlen($author) > 50) {
            throw new InvalidArgumentException(
                "The author is invalid.");
        }
        $this->author = $author;
        return $this;
    }
    
    public function getAuthor() {
        return $this->author;
    }
}

The interaction between the Post and Comment classes is trivial, but the Post class deserves an in-depth look. Effectively, it has been designed as a “classic” subject, hence providing the typical API which permits to attach/detach and notify observers at will.

PostComment类之间的交互是微不足道的,但是Post类值得深入研究。 实际上,它已被设计为“经典”主题,因此提供了典型的API,允许随意附加/分离并通知观察者。

The most interesting facet of this process is the implementation of setComment() where the actual inversion of control takes place. The method just fires a “pull” update to all the registered observers whenever a comment is added. This means that all the logic required for sending out the email notification is delegated to one or more external observers, thus offloading the dirty work from Post, keeping it focused on just its own business logic.

此过程最有趣的方面是setComment()的实现,其中实际发生了控件的反转。 每当添加评论时,该方法都会向所有注册的观察者触发“拉动”更新。 这意味着发送电子邮件通知所需的所有逻辑都委托给一个或多个外部观察者,从而减轻了Post工作负担,使之仅专注于自己的业务逻辑。

With this simple but effective schema of inversion of control in place, the only structure that needs to be added to the picture is at least one observer which should be responsible for dispatching the aforementioned email. To keep things easy to follow, I’m going to implement the observer as a thin entity living and breathing in the service layer.

通过这种简单而有效的控制反转方案,需要添加到图片中的唯一结构是至少一个观察者,该观察者应负责分发上述电子邮件。 为了使事情易于理解,我将实现观察者作为在服务层中生活和呼吸的瘦实体。

将控制权委派给外部环境–实施评论通知服务 (Delegating Control to the External Environment – Implementing a Comment Notification Service)

Building an observer service capable of triggering an email notification when a new comment is added to a blog post is a simple process, reduced to defining a class that implements the pertaining update() method. If you’re curious and want to see how the service in question looks, here it is:

构建一个能够在将新评论添加到博客帖子时触发电子邮件通知的观察程序服务是一个简单的过程,简化为定义实现相关update()方法的类。 如果您好奇并想查看所讨论的服务的外观,则为:

<?php
namespace Service;

class CommentService implements SplObserver
{
    public function update(SplSubject $post) {
        $subject = "New comment posted!";
        $message = "A comment has been made on a post entitled " .
            $post->getTitle();
        $headers = "From: "Notification System" <notify@example.com>rnMIME-Version: 1.0rn";
        if (!@mail("admin@example.com", $subject, $message, $headers)) {
            throw new RuntimeException("Unable to send the update.");
        }
    }
}

The CommentService class does exactly what it’s supposed to; it invokes its update() method to dispatch an email to the sysadmin each time a user drops a comment related to a given post.

CommentService类完全可以实现预期的功能。 每当用户删除与给定帖子相关的评论时,它都会调用其update()方法将电子邮件发送给sysadmin。

It’d be a lot easier to see the benefits brought by inversion of control in this situation if I showed you a script that puts all the sample classes to work, so below is some code:

如果我向您展示了一个脚本,可以使所有示例类正常工作,那么在这种情况下,控制反转带来的好处就容易多了,下面是一些代码:

<?php
use LibraryLoaderAutoloader,
    ModelPost,
    ModelComment,
    ServiceCommentService;
         
require_once __DIR__ . "/Library/Loader/Autoloader.php";
$autoloader = new Autoloader();
$autoloader->register();

$post = new Post(
    "A sample post",
    "This is the content of the sample post"
);

$post->attach(new CommentService());

$comment = new Comment(
    "A sample comment",
    "Just commenting on the previous post"
);

$post->setComment($comment);

Quite possibly, and this is just my personal preference, I don’t feel quite right injecting a service into the internals of a domain object. The powers that be have always proclaimed the domain layer must be agnostic about the service layer (unless you appeal to separated interfaces), and this one should lay down on an upper level interoperating with multiple clients. But in this case the approach isn’t really that sinful considering the Post class is just a dummy container for the registered observers which are only consumed when triggering event updates. Moreover, taking into account how neatly the responsibilities between the service in question and the Post class have been inverted, my complaint should be considered pretty much a picky whim.

很有可能,这只是我个人的喜好,将服务注入域对象的内部并不十分正确。 始终宣称域层的功能必须与服务层不可知(除非您诉诸于分离的接口),并且此功能应放在与多个客户端进行互操作的更高级别上。 但是在这种情况下,考虑到Post类只是已注册观察者的虚拟容器,这种方法并没有那么麻烦,只有在触发事件更新时才使用它们。 此外,考虑到所讨论的服务与邮政部门之间的职责已被巧妙地颠倒了,我的投诉应该被视为挑剔的想法。

总结思想 (Closing Thoughts)

Quite often considered an obscure, tangled concept, especially in PHP where many developers tend to intuitively associate the concept only with plain old Dependency Injection, Inversion of Control is a simple yet killer programming methodology which, when properly implemented, is a fantastic way for creating decoupled, orthogonal systems whose components can be easily tested in isolation.

通常,它被认为是一个晦涩,混乱的概念,尤其是在PHP中,许多开发人员倾向于将其仅与普通的旧式Dependency Injection直观地关联,控制反转是一种简单而致命的编程方法,当正确实现时,这是一种绝佳的创建方法解耦的正交系统,其组件可以轻松地进行隔离测试。

If you’re using Dependency Injection within your applications (you are, right?) then you should feel your coder’s instincts pretty well satisfied as you’re already exploiting the benefits that Inversion of Control provides. As I attempted to demonstrate before, however, there’s a wide array of situations where the approach fits well other than just managing class dependencies the right way. Event-Driven Design is certainly a good example.

如果您正在应用程序中使用依赖注入(是吧?),那么您应该已经对编码人员的直觉感到满意,因为您已经在利用控制反转提供的好处。 但是,正如我之前试图证明的那样,除了以正确的方式管理类依赖关系之外,还有很多情况都适合该方法。 事件驱动设计无疑是一个很好的例子。

Image via Fotolia

图片来自Fotolia

翻译自: https://www.sitepoint.com/inversion-of-control-the-hollywood-principle/

ioc - 控制反转

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值