题 什么是控制倒置?


控制反转(或IoC)在第一次遇到时会非常混乱。

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

1430
2017-08-06 03:35


起源


大多数答案的问题是使用的术语。什么是容器?反转?依赖?在没有大词的情况下以外行的方式解释它。 - kirk.burleson
另见Programmers.SE: 为什么Inversion of Control这样命名? - Izkata
它是依赖注入(DI) - 请参阅Martin Fowlers在这里的描述: martinfowler.com/articles/injection.html#InversionOfControl - Ricardo Sanchez
这张照片总结了一下 - totymedli
它是一个形容词,而不是一个名词,它不是一个东西,它是对代码所做更改的描述,其中流的控制在委托中,而不是容器中。 - Martin Spamer


答案:


控制反转(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 SpellChecker();),这意味着 TextEditor 类直接依赖于 SpellChecker 类。

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

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

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

这只是一个简单的例子 一系列好文章 由Simone Busoli更详细地解释它。


1140
2017-08-06 07:22



好清楚的例子。但是,假设不是要求将ISpellChecker接口传递给对象的构造函数,而是将其作为可设置属性(或SetSpellChecker方法)公开。这还会构成IoC吗? - devios1
chainguy1337 - 是的。使用这样的setter称为setter注入而不是构造函数注入(两种依赖注入技术)。 IoC是一种相当通用的模式,但依赖注入可以实现IoC - Schneider
尽管投票很多,但这个答案是不正确的。请参见 martinfowler.com/articles/injection.html#InversionOfControl。特别要注意的是“控制反转过于通用,因此人们会发现它令人困惑。因此,与各种IoC倡导者进行了大量讨论后,我们选择了依赖注入的名称”。 - Rogério
我同意@Rogeria。这并没有解释为什么它被称为IoC而且我对投票数量感到惊讶;-) - Aravind R. Yarram
我支持@Rogerio和@Pangea。这可能是一个很好的例子 构造函数注入 但对原始问题不是一个好的答案。 IoC,由定义 福勒,可以在不使用任何种类的注射的情况下实现,例如,通过使用 服务定位器 甚至是简单的继承。 - mtsz


控制反转是您在程序回调时获得的,例如像一个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

因此,现在控制被反转...用户不是以固定顺序接受用户输入,而是控制输入数据的顺序,以及何时将数据保存在数据库中。

基本上,具有事件循环,回调或执行触发器的任何内容都属于此类别。


495
2017-08-06 05:42



不要标记这个家伙。从技术上讲,他是对的 martinfowler.com/bliki/InversionOfControl.html IoC是一个非常普遍的校长。控制流由依赖注入“反转”,因为您已经有效地将依赖性委托给某个外部系统(例如IoC容器) - Schneider
同意Schneider的评论。 5 downvotes?心灵难以置信,因为这是唯一真正正确的答案。请注意开头:'喜欢 一个gui程序。'依赖注入只是IoC最常见的实现。 - Jeff Sternal
实际上,这是为数不多的之一 正确 anwsers!伙计们,IoC是 不 从根本上讲是依赖关系。一点也不。 - Rogério
+1 - 这是Martin Fowler对以下声明的一个很好的描述(带有示例) - “早期用户界面由应用程序控制。您将有一系列命令,如”输入名称“,”输入地址“;您的程序将驱动提示并获取每个响应。使用图形(甚至基于屏幕)的UI,UI框架将包含此主循环,而您的程序则为屏幕上的各个字段提供事件处理程序。程序被颠倒了,从你身边移到了框架上。“ - Ashish Gupta
我现在明白为什么它有时被称为“好莱坞原则:不要打电话给我们,我们会打电话给你” - Alexander Suraphel


什么是控制倒置?

如果您按照这两个简单的步骤操作,那么您已完成控制反转:

  1. 分离 什么来自的部分 什么时候做部分。
  2. 确保这件事 什么时候 部分知道为  尽可能的 什么 部分;反之亦然。

根据您用于实施的技术/语言,每种步骤都有几种可能的技术。

-

逆温 控制反转(IoC)的一部分是令人困惑的事情;因为 逆温 是相对的术语。理解IoC的最好方法就是忘掉那个词!

-

例子

  • 事件处理。事件处理程序(有待完成的部分) - 提升事件(何时可执行部分)
  • 接口。组件客户端(部分时间) - 组件接口实现(可执行部分)
  • xUnit fixure。 Setup和TearDown(有待完成的部分) - xUnit框架在开始时调用Setup,在结束时调用TearDown(when-to-do部分)
  • 模板方法设计模式。模板方法什么时候做的部分 - 原始子类实现什么做的部分
  • COM中的DLL容器方法。 DllMain,DllCanUnload等(有什么要做的部分) - COM / OS(什么时候做部分)

359
2017-07-22 17:34



你怎么说接口。当我们使用接口时,组件客户端(当工作时)没有意义(例如:依赖注入),我们只是将其抽象出来并为客户端提供添加任何实现的灵活性,但没有“何时”参与其中。我同意在“举办事件处理事件”时“何时”。 - YakRangi
通过'组件客户端',我指的是界面的用户/客户端。客户端知道“何时”触发“做什么”部分是否意图扩展功能。 - rpattabi
看看Martin Fowler撰写的精彩文章。他展示了接口如何成为控制反转的基本部分: martinfowler.com/articles/injection.html#InversionOfControl - rpattabi
两个第一句话很精彩。真棒!完美分离了什么时候做什么和做什么!我不知道为什么其他答案得到了许多赞成。他们只是谈论代码而没有任何理解。 - Amir Ziarati


控制反转是关于分离问题。

没有IoC: 你有一个 笔记本电脑 电脑,你不小心打破了屏幕。而且,你会发现相同型号的笔记本电脑屏幕在市场上无处可去。所以你被困住了。

有了IoC: 你有一个 桌面 电脑,你不小心打破了屏幕。您发现几乎可以从市场上购买任何桌面显示器,它可以很好地与您的桌面配合使用。

在这种情况下,您的桌面成功实现了IoC。它接受各种类型的显示器,而笔记本电脑没有,它需要一个特定的屏幕来修复。


90
2017-09-25 14:24



Jijong Hui这个清除了我的脑袋。 - Arun Prasad E S
@Luo Jiong Hui不错的解释。 - Sach
大多数设计模式(如果不是全部的话)在我们的日常生活中都有我们看到和理解的对应物。理解设计模式的最有效方式是了解他们的日常生活对应物。我相信有很多。 - Luo Jiong Hui


控制反转(或IoC)即将发布 获得自由 (你结婚了,你失去了自由,你被控制了。你离婚了,你刚刚实施了控制倒置。这就是我们所说的,“脱钩”。良好的计算机系统阻止了一些非常密切的关系。) 更灵活 (办公室的厨房只提供干净的自来水,这是您想要饮用的唯一选择。您的老板通过设置新的咖啡机实现了控制反转。现在您可以灵活选择自来水或咖啡。 )和 减少依赖 (你的伴侣有工作,你没有工作,你在经济上依赖你的伴侣,所以你得到了控制。你找到了工作,你实施了控制倒置。良好的计算机系统鼓励依赖。)

当您使用台式计算机时,您已经奴役(或说,控制)。你必须坐在屏幕前看看它。使用键盘键入并使用鼠标进行导航。一个编写得很糟糕的软件可以让你更加依赖。如果用笔记本电脑替换桌面,那么你有点颠倒控制。你可以轻松地拿走它并四处走动。因此,现在您可以控制计算机的位置,而不是控制计算机的计算机。

通过实现控制反转,软件/对象消费者可以获得对软件/对象的更多控制/选项,而不是受控制或具有更少的选项。

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

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

虽然工人将管理项目的控制权交给管理者(框架)。但是让一些专业人士帮忙是件好事。这是IoC真正来自的概念。

具有MVC体系结构的现代Web应用程序依赖于框架来进行URL路由并将控制器放置到适合框架调用的位置。

依赖注入和控制反转是相关的。依赖注入是在  等级和控制反转是在  水平。你必须吃每一口(实施DI)才能吃完饭(实施IoC)。


80
2018-03-04 19:33



我投票支持比较DI婚姻和IoC离婚。 - Marek Bar
该死的......很喜欢!!! - Gowtham GS


在使用Inversion of Control之前,你应该清楚它有它的优点和缺点,你应该知道为什么要使用它。

优点:

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

缺点:

  • IoC不仅可以反转程序中的控制流程,还可以显着地覆盖它。这意味着您不能再只是读取代码并从一个地方跳转到另一个地方,因为通常在代码中的连接不再在代码中。相反,它在XML配置文件或注释中以及IoC容器的代码中解释这些元数据。
  • 出现了一类新的错误,你的XML配置或注释错误,你可以花很多时间找出你的IoC容器在某些条件下为你的一个对象注入空引用的原因。

就个人而言,我看到了IoC的优点,我真的很喜欢它们,但我倾向于尽可能地避免使用IoC,因为它会将您的软件变成一个不再构成“真正”程序的类集合,而只需要将它们组合在一起XML配置或注释元数据会在没有它的情况下掉落(和掉落)。


73
2018-02-12 14:31



第一个骗局不正确。理想情况下,代码中只应使用1个IOC容器,这是您的主要方法。其他一切都应该从那里开始 - mwjackson
我认为他的意思是,你不能只读:myService.DoSomething()并转到DoSomething的定义,因为对于IoC,myService只是一个接口,你不知道实际的实现,除非你去看看它在xml配置文件或你的ioc设置的主要方法。 - chrismay
这就是Resharper帮助的地方 - 针对界面“点击进入实施”。避免IoC(或者更具体地说,来自您的示例中的DI)可能也意味着您没有正确测试 - IThasTheAnswer
回覆: 它将你的软件变成了一个不再构成“真正”程序的类集合,而只是需要通过XML配置或注释元数据组合在一起的东西,并且如果没有它就会掉落(和掉落)  - 我认为这是非常误导的。任何编写在框架之上的程序都可以这样说。与良好的IoC容器的不同之处在于,如果您的程序设计和编写得很好,您应该能够将其取出并在代码中进行最少的更改,或者完全丢弃IoC并手动构建您的对象。 - The Awnry Bear
很高兴看到像这样的现实世界的答案!我认为有很多经验丰富的程序员,对于面向对象的设计和TDD实践感到满意,在这个“IoC”流行语被发明之前,已经使用了接口,工厂模式,事件驱动模型和模拟有意义的地方。不幸的是,如果你不使用他们喜欢的框架,太多的开发人员/“架构师”会声称做法不好。我更喜欢更好的设计,使用内置的语言概念和工具,以复杂性的一小部分实现相同的目标,即没有像你说的那样“蒙上阴影”:-) - Tony Wall


  1. 维基百科文章。对我而言,控制反转正在转变您按顺序编写的代码并将其转换为委托结构。您的程序不是使用程序显式控制所有内容,而是设置一个具有某些功能的类或库,以便在发生某些事情时调用它们。

  2. 它解决了代码重复问题。例如,在过去,您将手动编写自己的事件循环,轮询系统库以查找新事件。如今,大多数现代API都只是告诉系统库您感兴趣的事件,它会在发生时告知您。

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

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


55
2017-08-06 04:33



我发现维基百科的文章非常令人困惑,需要修复。查看讨论页面,了解笑声。 - devios1


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

就像在TextEditor的这个例子中一样:如果你只有一个SpellChecker可能没有必要使用IoC?除非您需要编写单元测试或其他东西......

无论如何:要合理。设计模式是 良好做法 但不要传讲圣经。不要把它粘在任何地方。


39
2017-08-06 22:08



你怎么知道你只有一个拼写检查器? - Trace


假设你是一个对象。你去餐馆吃饭:

没有IoC:你要求“苹果”,当你提出更多问题时,你总会得到苹果。

有了IoC:你可以要求“水果”。每次送达时,您都可以获得不同的水果。例如,苹果,橙子或西瓜。

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


35
2017-09-25 14:00