什么是控制反转?

第一次遇到控制反转(IoC)时可能会造成很大的混乱。

  1. 它是什么?
  2. 它解决什么问题?
  3. 什么时候合适,什么时候不合适?

答案

控制反转(IoC)和依赖注入(DI)模式都是关于从代码中删除依赖的。

例如,假设您的应用程序具有文本编辑器组件,而您想提供拼写检查。您的标准代码如下所示:

public class TextEditor {

    private SpellChecker checker;

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

我们在这里所做的创建了TextEditorSpellChecker之间的依赖关系。在 IoC 场景中,我们改为执行以下操作:

public class TextEditor {

    private IocSpellChecker checker;

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

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

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

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

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

当程序回调(例如 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

因此,现在控制权被颠倒了... 而不是计算机按照固定的顺序接受用户输入,而是由用户控制输入数据的顺序以及何时将数据保存在数据库中。

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

什么是控制反转?

如果遵循以下两个简单步骤,就可以完成控制反转:

  1. 分开什么 -to - 做部分来自 -to - 做兼职。
  2. 确保一部分都知道尽量 什么部分; 反之亦然。

根据用于实现的技术 / 语言,这些步骤中的每一种都有几种技术。

-

控制反转(IoC)的反转部分令人困惑;因为反转是相对项。理解 IoC 的最好方法就是忘记这个词!

-

例子

  • 事件处理。事件处理程序(要做的部分)- 引发事件(要做的部分)
  • 接口。组件客户端(待办事项)- 组件接口实现(待办事项)
  • xUnit 固定装置。 Setup 和 TearDown(要做的部分)-xUnit 框架在开始时调用 Setup,在结束时调用 TearDown(当做部分)
  • 模板方法设计模式。模板方法何时做部分 - 基本子类实现要做部分
  • COM 中的 DLL 容器方法。 DllMain,DllCanUnload 等(待办事项)-COM / OS(待办事项)

控制反转是关于分离关注点。

没有 IoC 的情况 :您有一台笔记本电脑,并且不小心打破了屏幕。而且,您发现市场上没有同型号的笔记本电脑屏幕。所以你被困住了。

使用 IoC :您有一台台式计算机,并且不小心打破了屏幕。您会发现您几乎可以从市场上买到任何台式机显示器,并且它可以很好地与您的台式机配合使用。

在这种情况下,您的桌面成功实现了 IoC。它可以接受多种类型的显示器,而笔记本电脑则不能,它需要特定的屏幕才能固定。

控制反转(或 IoC)是关于获得自由 (您结婚,失去自由并受到控制。离婚,您刚刚实现了控制反转。这就是我们所说的 “分离”。好的计算机系统不鼓励某些亲密关系。) 更大的灵活性 (办公室中的厨房仅提供干净的自来水,这是您喝酒时的唯一选择。老板通过安装新的咖啡机实施了控制反转。选择自来水或咖啡的灵活性。)并且依赖性较低 (您的伴侣有工作,您没有工作,您在经济上依赖伴侣,因此您受到控制。找到工作,就实施了 Inversion of 控制。良好的计算机系统鼓励相互依赖。)

使用台式计算机时,您已成为从属(或受控)。您必须坐在屏幕前看一下。使用键盘键入并使用鼠标进行导航。一个写得不好的软件甚至可以奴役您。如果用笔记本电脑代替台式机,则控制会有所倒置。您可以轻松地随身携带它。因此,现在您可以控制计算机的位置,而不是由计算机控制它。

通过实施控制反转,软件 / 对象使用者可以获得比软件 / 对象更多的控件 / 选项,而不是被控制或具有更少的选项。

考虑到以上想法。我们仍然错过了 IoC 的关键部分。在 IoC 场景中,软件 / 对象使用者是一个复杂的框架。这意味着您创建的代码不会被您自己调用。现在,让我们解释一下为什么这种方式对 Web 应用程序更好。

假设您的代码是一群工人。他们需要制造一辆汽车。这些工人需要一个地方和工具(一个软件框架)来制造汽车。 传统的软件框架就像带有许多工具的车库。因此,工人需要自己制定计划并使用工具来制造汽车。造车不是一件容易的事,对工人来说,正确地计划和合作确实非常困难。 现代化的软件框架将像拥有所有设施和经理的现代化汽车工厂一样。工人不必制定任何计划,经理(框架的一部分,他们是最聪明的人,并且制定了最复杂的计划)将帮助协调工作,以便工人知道何时完成工作(框架调用您的代码)。工作人员只需要足够灵活即可使用管理人员提供给他们的任何工具(通过使用依赖注入)。

尽管工人将管理项目的控制权交给了经理(框架)。但是有一些专业人士的帮助是一件好事。这是 IoC 的概念真正源于此。

具有 MVC 架构的现代 Web 应用程序依赖于框架进行 URL 路由,并放置控制器以供框架调用。

依赖注入和控制反转相关。依赖注入在微观级别,而控制反转在宏观级别。您必须吃饱每一口(实施 DI)才能吃完饭(实施 IoC)。

在使用 Inversion of Control 之前,您应该充分了解它有其优点和缺点的事实,并且应该知道为什么要使用它。

优点:

  • 您的代码被解耦,因此您可以轻松地将接口的实现与其他实现互换
  • 它是针对接口而非实现进行编码的强大动力
  • 为代码编写单元测试非常容易,因为它只依赖于其构造函数 / 设置器中接受的对象,并且可以轻松地使用正确的对象来单独初始化它们。

缺点:

  • IoC 不仅会颠倒程序中的控制流,还会使它变得相当模糊。这意味着您不再只能阅读代码并从一个地方跳到另一个地方,因为通常在您的代码中存在的连接不再在代码中。而是在 XML 配置文件或注释中以及在解释这些元数据的 IoC 容器的代码中。
  • 出现了一类新的错误,您在该错误中将 XML 配置或批注弄错了,您可以花费大量时间来找出为什么 IoC 容器在特定条件下将空引用注入到一个对象中的原因。

我个人看到了 IoC 的优点,并且我真的很喜欢它们,但是我倾向于尽可能避免使用 IoC,因为它将您的软件变成一个类的集合,这些类不再构成 “真正的” 程序,而只是需要将它们组合在一起 XML 配置或注释元数据,如果没有它,它会崩溃。

  1. 维基百科文章 。对我而言,控制权的转换是将您顺序编写的代码转换为委托结构。程序没有设置明确控制所有内容的程序,而是设置了具有某些功能的类或库,以在发生某些事情时调用它们。

  2. 它解决了代码重复。例如,在过去,您将手动编写自己的事件循环,在系统库中轮询新事件。如今,大多数现代 API 只需告诉系统库您对哪些事件感兴趣,它就会在事件发生时通知您。

  3. 控制反转是减少代码重复的一种实用方法,如果您发现自己复制了整个方法而只更改了一小段代码,则可以考虑通过控制反转解决它。通过代理,接口甚至原始函数指针的概念,在许多语言中,控制反转都变得很容易。

    不适合在所有情况下使用,因为以这种方式编写程序时,流程很难遵循。这是编写将被重用的库时设计方法的一种有用方法,但是除非您真正解决了代码重复问题,否则应在您自己程序的核心中谨慎使用它。

但是我认为您必须非常小心。如果您将过度使用此模式,则将进行非常复杂的设计,甚至是更加复杂的代码。

就像本例中使用 TextEditor 的示例一样:如果您只有一个 SpellChecker,也许不是真的需要使用 IoC 吗?除非您需要编写单元测试之类的东西,否则...

无论如何:合理。设计模式是好的做法,但不是要宣扬的圣经。不要将其粘在任何地方。

假设您是一个对象。然后您去一家餐厅:

如果没有 IoC :您要求 “苹果”,而当您提出更多要求时,您总是会得到苹果。

使用 IoC :您可以要求 “水果”。每次上菜都可以得到不同的水果。例如苹果,橙子或西瓜。

因此,显然,当您喜欢这些品种时,IoC 是首选。

我的 IoC / DI 正在推出对调用对象的依赖关系。超级简单。

绝妙的答案是能够在打开汽车之前就将其换掉。如果一切都正确(界面),那么您就很好。