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

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


当前回答

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

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

其他回答

控制反转(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是关于颠倒代码和第三方代码(库/框架)之间的关系:

在正常的软件开发中,您编写main()方法并调用“library”方法。您可以控制:)在IoC中,“框架”控制main()并调用您的方法。该框架处于受控状态:(

DI(依赖注入)是关于控件在应用程序中如何流动的。传统的桌面应用程序具有从应用程序(main()方法)到其他库方法调用的控制流,但DI控制流是反向的,框架负责启动应用程序、初始化应用程序并在需要时调用方法。

最终,你总会赢:)

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

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

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

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

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

由于这个问题已经有很多答案,但没有一个显示反转控制项的分解,我认为有机会给出一个更简洁和有用的答案。

控制反转是一种实现依赖反转原理(DIP)的模式。DIP声明如下:1。高级模块不应依赖于低级模块。两者都应该依赖于抽象(例如接口)。2.摘要不应依赖于细节。细节(具体实现)应该依赖于抽象。

控制反转有三种类型:

界面反转提供程序不应定义接口。相反,使用者应该定义接口,提供者必须实现它。接口反转允许消除每次添加新提供者时修改使用者的必要性。

流量反演更改流量控制。例如,您有一个控制台应用程序,要求输入许多参数,在输入每个参数后,您必须按enter键。您可以在此处应用Flow Inversion,并实现桌面应用程序,用户可以选择输入参数的顺序,用户可以编辑参数,在最后一步,用户只需按Enter键一次。

创建反转它可以通过以下模式实现:工厂模式、服务定位器和依赖注入。创建反转有助于消除类型之间的依赖关系,将依赖关系对象创建过程移到使用这些依赖关系对象的类型之外。为什么依赖关系不好?这里有几个例子:在代码中直接创建一个新对象会使测试更加困难;不重新编译就不可能更改程序集中的引用(违反OCP原则);你不能轻易地用web UI替换桌面UI。

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

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

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

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