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

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


当前回答

控制反转是将控制权从库转移到客户端。当我们讨论将函数值(lambda表达式)注入(传递)到控制(改变)库函数行为的高阶函数(库函数)中的客户端时,它更有意义。

因此,这个模式的一个简单实现(具有巨大的含义)是一个更高阶的库函数(它接受另一个函数作为参数)。库函数通过赋予客户端提供“控制”函数作为参数的能力来传递对其行为的控制。

例如,“map”、“flatMap”等库函数是IoC实现。

当然,例如,有限的IoC版本是布尔函数参数。客户端可以通过切换布尔参数来控制库函数。

将库依赖项(承载行为)注入到库中的客户端或框架也可以被视为IoC

其他回答

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

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

最小的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);

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

控制反转(IoC)模式是关于提供任何类型的回调,它“实现”和/或控制反应,而不是直接执行自己(换句话说,反转和/或将控制重定向到外部处理器/控制器)。依赖注入(DI)模式是IoC模式的一个更具体的版本,它完全是从代码中删除依赖项。

每个DI实现都可以被视为IoC,但不应该称之为IoC。因为实现依赖注入比回调更困难(不要使用通用术语“IoC”来降低产品的价值)。

例如DI,假设您的应用程序有一个文本编辑器组件,并且您希望提供拼写检查。你的标准代码应该是这样的:

public class TextEditor {

    private SpellChecker checker;

    public TextEditor() {
        this.checker = new SpellChecker();
    }
}

我们在这里所做的工作在TextEditor和SpellChecker之间创建了依赖关系。在IoC场景中,我们会这样做:

public class TextEditor {

    private IocSpellChecker checker;

    public TextEditor(IocSpellChecker checker) {
        this.checker = checker;
    }
}

在第一个代码示例中,我们正在实例化SpellChecker(this.checker=new SpellCheckr();),这意味着TextEditor类直接依赖于SpellChecker类。

在第二个代码示例中,我们通过在TextEditor的构造函数签名中使用SpellChecker依赖类(而不是在类中初始化依赖项)来创建抽象。这允许我们调用依赖项,然后将其传递给TextEditor类,如下所示:

SpellChecker sc = new SpellChecker(); // dependency
TextEditor textEditor = new TextEditor(sc);

现在,创建TextEditor类的客户端可以控制使用哪个SpellChecker实现,因为我们正在将依赖项注入TextEditor签名中。


注意,就像IoC是许多其他模式的基础一样,上面的示例只是依赖注入类型中的一种,例如:

构造函数注入。IocSpellChecker的实例将自动传递给构造函数,或手动传递给构造函数。沉淀剂注入。IocSpellChecker的实例将通过setter方法或公共属性传递。服务查找和/或服务定位器其中TextEditor将向已知的提供者请求IocSpellChecker类型的全局使用的实例(服务)(这可能不存储所述实例,而是一次又一次地询问提供者)。

IoC原则有助于设计松散耦合的类,使其可测试、可维护和可扩展。

假设你是一个物体。然后你去餐馆:

没有IoC:你要求“苹果”,当你要求更多时,你总是得到苹果。

与IoC:你可以要求“水果”。每次上桌你都可以得到不同的水果。例如,苹果、橙子或西瓜。

所以,很明显,当你喜欢品种时,IoC是首选。

我喜欢这样的解释: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更灵活,可以在运行时决定实现隔离代码单元,更容易测试

...