当第一次遇到控制反转(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更灵活,可以在运行时决定实现隔离代码单元,更容易测试

...

其他回答

“IoC”这个首字母缩略词和它所代表的名字似乎最让人困惑的是,这个名字太迷人了——几乎是一个喧嚣的名字。

我们真的需要一个名字来描述过程式编程和事件驱动编程之间的区别吗?好吧,如果我们需要的话,但我们是否需要选择一个全新的“比生活更大”的名字,它让人困惑而不是解决问题?

我知道这里已经给出了答案。但我仍然认为,关于控制反转的一些基础知识必须在这里详细讨论,以供未来读者参考。

控制反转(IoC)建立在一个叫做好莱坞原理的非常简单的原理上。上面说,

不要给我们打电话,我们会给你打电话

这意味着不要去好莱坞实现你的梦想,如果你值得,那么好莱坞会找到你,让你的梦想成真。差不多颠倒了,嗯?

现在,当我们讨论IoC的原理时,我们常常忘记好莱坞。对于IoC来说,必须有三个要素,一个好莱坞,一个你和一个完成你梦想的任务。

在我们的编程世界中,Hollywood代表一个通用框架(可能由您或其他人编写),您代表您编写的用户代码,任务代表您希望用代码完成的任务。现在,你永远不会自己触发任务,而不是在IoC!相反,你已经设计好了所有的东西,这样你的框架就会触发你的任务。因此,您已经构建了一个可重用的框架,它可以使某人成为英雄或另一人成为恶棍。但这一框架始终处于主导地位,它知道什么时候该挑选某人,而某人只知道自己想要成为什么样的人。

这里将给出一个真实的例子。假设您想开发一个web应用程序。因此,您可以创建一个框架来处理web应用程序应该处理的所有常见事务,如处理http请求、创建应用程序菜单、服务页面、管理cookie、触发事件等。

然后你在你的框架中留下一些钩子,你可以在那里放置更多的代码来生成自定义菜单、页面、cookie或记录一些用户事件等。在每次浏览器请求时,你的框架都会运行并执行你的自定义代码,如果勾住了,然后将其发送回浏览器。

所以,这个想法非常简单。与其创建一个控制一切的用户应用程序,不如先创建一个可重用的框架来控制一切,然后编写自定义代码并将其连接到框架以及时执行这些代码。

Laravel和EJB就是这样一个框架的例子。

参考:

https://martinfowler.com/bliki/InversionOfControl.html

https://en.wikipedia.org/wiki/Inversion_of_control

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

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

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

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

控制反转(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类型的全局使用的实例(服务)(这可能不存储所述实例,而是一次又一次地询问提供者)。

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

...