当第一次遇到控制反转(IoC)时,它可能非常令人困惑。

这是怎么一回事?它解决了哪个问题?什么时候使用合适,什么时候不合适?


当前回答

控制反转是当程序回调时得到的结果,例如gui程序。

例如,在旧学校菜单中,您可能有:

print "enter your name"
read name
print "enter your address"
read address
etc...
store in database

从而控制用户交互的流程。

在GUI程序或类似程序中,我们会说:

when the user types in field a, store it in NAME
when the user types in field b, store it in ADDRESS
when the user clicks the save button, call StoreInDatabase

所以现在控制反转了。。。代替计算机以固定的顺序接受用户输入,用户控制输入数据的顺序以及数据保存在数据库中的时间。

基本上,任何带有事件循环、回调或执行触发器的东西都属于这一类。

其他回答

对我来说,IoC/DI正在向调用对象推出依赖项。超级简单。

非技术性的答案是,在你打开引擎之前,你可以更换汽车的引擎。如果一切正常(界面),你就很好了。

我喜欢这样的解释:http://joelabrahamsson.com/inversion-of-control-an-introduction-with-examples-in-net/

它开始很简单,还显示了代码示例。

消费者X需要被消费的类Y来完成某件事。这一切都很好,很自然,但X真的需要知道它使用Y吗?

如果X知道它使用的东西具有Y的行为、方法、财产等,而不知道谁真正实现了这些行为,这还不够吗?

通过提取X在Y中使用的行为的抽象定义(如下面的I所示),并让消费者X使用该行为的实例而不是Y,它可以继续做它所做的事情,而不必知道Y的细节。

在上图中,Y实现了I,X使用了I的一个实例。虽然很可能X仍然使用Y,但有趣的是X并不知道这一点。它只知道它使用了实现I的东西。

阅读文章了解更多信息和好处描述,如:

X不再依赖于Y更灵活,可以在运行时决定实现隔离代码单元,更容易测试

...

控制权倒置是项目责任转移的一个指标。

当依赖项被授予直接作用于调用者空间的能力时,每次都会发生控制反转。

最小的IoC是通过引用传递变量,让我们先看看非IoC代码:

function isVarHello($var) {
    return ($var === "Hello");
}

// Responsibility is within the caller
$word = "Hello";
if (isVarHello($word)) {
    $word = "World";
}

现在,让我们通过将结果的责任从调用者转移到依赖项来反转控制:

function changeHelloToWorld(&$var) {
    // Responsibility has been shifted to the dependency
    if ($var === "Hello") {
        $var = "World";
    }
}

$word = "Hello";
changeHelloToWorld($word);

下面是另一个使用OOP的示例:

<?php

class Human {
    private $hp = 0.5;

    function consume(Eatable $chunk) {
        // $this->chew($chunk);
        $chunk->unfoldEffectOn($this);
    }

    function incrementHealth() {
        $this->hp++;
    }
    function isHealthy() {}
    function getHungry() {}
    // ...
}

interface Eatable {
    public function unfoldEffectOn($body);
}

class Medicine implements Eatable {
    function unfoldEffectOn($human) {
        // The dependency is now in charge of the human.
        $human->incrementHealth();
        $this->depleted = true;
    }
}

$human = new Human();
$medicine = new Medicine();
if (!$human->isHealthy()) {
    $human->consume($medicine);   
}

var_dump($medicine);
var_dump($human);

*)免责声明:现实世界中的人类使用消息队列。

所以上面的数字1。什么是控制反转?维护是它为我解决的首要问题。它保证我使用的是接口,这样两个类就不会彼此亲密。

使用温莎城堡这样的容器,它可以更好地解决维护问题。能够在不更改一行代码的情况下,将一个连接到数据库的组件替换为一个使用基于文件的持久性的组件,这是非常棒的(配置更改完成了)。

一旦你进入泛型,它会变得更好。想象一下,拥有一个接收记录并发布消息的消息发布者。它不在乎它发布了什么,但它需要一个映射器将记录中的内容转换为消息。

public class MessagePublisher<RECORD,MESSAGE>
{
    public MessagePublisher(IMapper<RECORD,MESSAGE> mapper,IRemoteEndpoint endPointToSendTo)
    {
      //setup
    }
}

我写过一次,但现在如果我发布不同类型的消息,我可以向这组代码中注入许多类型。我还可以编写映射器,将相同类型的记录映射到不同的消息。将DI与Generics结合使用使我能够编写很少的代码来完成许多任务。

哦,是的,存在可测试性问题,但它们比IoC/DI的优势更为次要。

我绝对喜欢IoC/DI。

3.当你有一个中等规模的项目时,它会变得更加合适。我会说,当你开始感到疼痛时,它就变得合适了。

我在这里找到了一个非常清楚的例子,它解释了“控制是如何颠倒的”。

经典代码(无依赖注入)

以下是不使用DI的代码大致工作原理:

应用程序需要Foo(例如控制器),因此:应用程序创建Foo应用程序调用FooFoo需要Bar(例如服务),因此:Foo创建BarFoo调用BarBar需要Bim(服务、存储库…),因此:条形图创建Bim酒吧有点事

使用依赖注入

以下是使用DI的代码大致工作原理:

应用程序需要Foo,需要Bar,需要Bim,因此:应用程序创建Bim应用程序创建Bar并赋予它Bim应用程序创建Foo并给它Bar应用程序调用FooFoo调用Bar酒吧有点事

依赖项的控制是从一个被调用到另一个调用的。

它解决了什么问题?

依赖注入使得可以很容易地与注入类的不同实现进行交换。在单元测试时,您可以注入一个虚拟实现,这使测试更加容易。

例如:假设您的应用程序将用户上传的文件存储在Google Drive中,使用DI,您的控制器代码可能如下所示:

class SomeController
{
    private $storage;

    function __construct(StorageServiceInterface $storage)
    {
        $this->storage = $storage;
    }

    public function myFunction () 
    {
        return $this->storage->getFile($fileName);
    }
}

class GoogleDriveService implements StorageServiceInterface
{
    public function authenticate($user) {}
    public function putFile($file) {}
    public function getFile($file) {}
}

当你的需求发生变化时,比如说,你被要求使用Dropbox而不是GoogleDrive。您只需要为StorageServiceInterface编写一个dropbox实现。只要Dropbox实现符合StorageServiceInterface,就不必对控制器进行任何更改。

测试时,您可以使用虚拟实现为StorageServiceInterface创建模拟,其中所有方法都返回null(或根据测试要求的任何预定义值)。

相反,如果您有一个控制器类来构造具有如下新关键字的存储对象:

class SomeController
{
    private $storage;

    function __construct()
    {
        $this->storage = new GoogleDriveService();
    }

    public function myFunction () 
    {
        return $this->storage->getFile($fileName);
    }
}

当您想要使用Dropbox实现进行更改时,必须替换构建新GoogleDriveService对象的所有行,并使用DropboxService。此外,在测试SomeController类时,构造函数总是期望GoogleDriveService类,并触发该类的实际方法。

什么时候合适,什么时候不合适?在我看来,当您认为类有(或可能有)替代实现时,您可以使用DI。