什么是控制反转?


1825

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

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

292
这些答案大多数的问题是所使用的术语。什么是容器?反转?依赖?用通俗易懂的语言来解释它,不要大声疾呼。
kirk.burleson

13
另请参阅Programmers.SE: 为什么以这种方式命名控制反转?
2013年

8
这是依赖注入(DI)-请参阅Martin Fowlers的描述:martinfowler.com/articles/injection.html#InversionOfControl
Ricardo Sanchez


它是一个形容词,不是一个名词,它不是事物,它是对代码所做的更改的描述,其中对流的控制在委托中,而不是在容器中。
马丁·斯帕默

Answers:


1522

控制反转(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;
    }
}

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

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

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

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


50
很好的例子。但是,假设不要求将ISpellChecker接口传递给对象的构造函数,而是将其公开为settable属性(或SetSpellChecker方法)。这仍会构成IoC吗?
devios1

25
chainguy1337-是的。与构造函数注入(两种依赖注入技术)相对,使用类似这样的setter称为setter注入。IoC是一种相当通用的模式,但是依赖项注入可实现IoC
Jack Ukleja 09年

257
尽管有很多赞成票,但这个答案是不正确的。请参阅 martinfowler.com/articles/injection.html#InversionOfControl。特别要注意的部分是“控制反转过于笼统,因此人们会感到困惑。因此,与各种IoC倡导者进行了很多讨论,我们决定使用依赖注入这个名称。”
罗杰里奥(Rogério)2010年

45
我同意@Rogeria。这并不能解释为什么它被称为IoC,而我对上数投票感到惊讶;-)
Aravind Yarram 2010年

31
我支持@Rogerio和@Pangea。这可能是构造函数注入的好例子,但不是原始问题的好答案。Fowler定义的IoC 无需使用任何类型的注入即可实现,例如通过使用服务定位器甚至简单的继承。
mtsz 2011年

642

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

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

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


161
不要把这个家伙打倒。从技术上讲,他是正确的martinfowler.com/bliki/InversionOfControl.html IoC是非常通用的主体。控制流因依赖关系注入而“反转”,因为您已将依赖关系有效地委派给了某些外部系统(例如IoC容器)
Jack Ukleja 09年

38
同意施耐德的评论。5下注?头脑很困惑,因为这是唯一正确的答案。注意开头:“ gui程序”。依赖注入只是IoC最常见的实现。
杰夫·斯坦恩

40
确实,这是为数不多的正确答案之一!伙计们,IoC 根本不是关于依赖关系的。一点也不。
罗杰里奥(Rogério)2010年

13
+1-这是马丁·福勒(Martin Fowler)以下陈述的良好描述(带有示例)-“早期用户界面由应用程序控制。您将具有一系列命令,例如“输入名称”,“输入地址”;程序将驱动提示并响应每个提示。图形用户界面(甚至基于屏幕的用户界面)的UI框架将包含此主循环,而您的程序将为屏幕上的各个字段提供事件处理程序。程序被倒置,从您移至框架。”
Ashish Gupta,2010年

14
我现在明白了为什么有时将它戏称为“好莱坞原则:不要打电话给我们,我们会打电话给您”
Alexander Suraphel 2013年

419

什么是控制反转?

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

  1. 分开什么 -to-做部分来自 -to-做兼职。
  2. 确保部分知道的地了解哪些部分; 反之亦然。

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

-

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

-

例子

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

您怎么说界面。当我们使用接口时,组件客户端(当做部分)“何时”是没有意义的(例如,依赖注入),我们只是将其抽象出来,并为客户端提供了添加任何实现的灵活性,但是没有“何时”参与其中。对于事件处理引发事件,我同意“何时”。
OldSchool's

“组件客户端”是指界面的用户/客户端。客户端知道“何时”触发“做什么”部分,无论目的是扩展功能还是不扩展。
rpattabi'5

看看Martin Fowler的这篇精彩文章。他展示了界面如何构成控制反转的基本部分:martinfowler.com/articles/injection.html#InversionOfControl
rpattabi

前两个句子很出色。太棒了!! 通过何时做与如何做完美分开!我不知道为什么其他答案会得到如此多的赞誉。他们只是在说代码而没有任何理解。
阿米尔·齐亚拉蒂

2
我喜欢这种解释,what-to-do而且when-to-do
KimchiMan

164

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

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

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

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


1
@罗炯慧不错的解释。
萨钦(Sachin)

3
大多数(即使不是全部)设计模式在我们的日常生活中都有与之相似的事物,我们对此非常了解和理解。理解设计模式的最有效方法是了解他们的日常生活。而且我相信有很多。
罗炯辉

6
错误的答案。您在解释依赖注入而不是IoC。请参阅上面的答案,
MickyD '18

2
我同意。这是DI,而不是IoC。由于它是一种简单的方法,因此仍然值得赞扬,但却有助于扩展对该主题的理解。
三月

它解释了依赖项注入。不是Ioc。但是很好的和清晰的解释。
Chamin Wickramarathna

119

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

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

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

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

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

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

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

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


20
我投票赞成将DI与婚姻相比较,将IoC与离婚相提并论。
Marek Bar

“尽管工人将高层管理项目的控制权交给了经理(框架)。但是最好有一些专业人士的帮助。这才是IoC真正的概念。”-首先,控制权是与经理。您能解释一下该控件如何反转吗?在专业人员(什么样的专业人员)的帮助下?怎么样 ?
Istiaque Ahmed

@Istiaque Ahmed,与工人完全掌控一切的车库相比,现代汽车工厂的经理控制着生产。因此,现在工人受到控制而不是控制。请注意,在这种情况下,经理是现代汽车工厂的一部分,而不是工人的一部分。专业人士是在规划和制造汽车方面专业的管理人员。
罗炯辉

1
给已婚人士的信息:现在不要离婚,您的孩子班级可能还会实施IoC。
朱利安

是的,这也是我对婚姻的
唯一

94

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

优点:

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

缺点:

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

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


3
第一个错误是不正确的。理想情况下,在代码中只能使用1个IOC容器,这是您的主要方法。其他一切都应从那里层叠下来
mwjackson 2010年

23
我认为他的意思是,您不能仅仅阅读:myService.DoSomething()并转到DoSomething的定义,因为在IoC中,myService只是一个接口,实际的实现对您来说是未知的,除非您去看看将其保存在xml配置文件中或设置ioc的主要方法中。
chrismay 2011年

7
这就是Resharper帮助的地方-在界面上“点击执行”。避免使用IoC(或更具体地讲,使用示例中的DI)也可能意味着您没有正确测试
IThasTheAnswer 2011年

10
回复:它将您的软件变成一个类的集合,这些类不再构成一个“真实的”程序,而只是需要通过XML配置或注释元数据组合在一起的类,如果没有它,它们将崩溃(或崩溃) -我认为这非常误导。对于在框架之上编写的任何程序都可以这样说。良好的IoC容器的区别在于,如果您的程序设计和编写得当,您应该能够以最小的代码改动将其取出并放入另一个程序中,或者完全抛弃IoC并手动构建对象。
Awnry熊,2012年

7
很高兴看到这样的真实答案!我认为有很多经验丰富的程序员,对面向对象的设计和TDD实践很满意,在发明“ IoC”流行语之前就已经使用了接口,工厂模式,事件驱动的模型和有意义的模拟。不幸的是,如果您不使用他们偏爱的框架,那么太多的开发人员/“架构师”会说不好的作法。我更喜欢一种更好的设计,使用内置的语言概念和工具来实现相同的目标,而复杂度却很小,即不会像您所说的那样“模糊”实现:-)
Tony Wall,

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

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

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

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


37
我发现Wikipedia的文章非常令人困惑,需要修正。查看讨论页面以获取笑声。
devios1

编写自己的事件循环可能仍然是控制反转,如果该事件循环代替了框架,而其余代码使用的是IoC原理,则您已经编写了自己的框架。这实际上不是一件坏事,它以增加一点编码的代价提高了可读性(也不是总是很合适)。
lijat

45

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

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

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


3
您怎么知道您只有一个拼写检查器?
追踪

@Trace例如,您可能知道要编写的程序将如何使用。还有一些诸如依赖注入的技术是如此便宜,以至于很少有理由在这种情况下不使用它们。
lijat

44

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

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


44

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

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

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

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


26
  1. 控制反转是用于解耦系统中组件和层的一种模式。该模式是通过在构造组件时将依赖项注入到组件中来实现的。这些依赖关系通常被提供为进一步去耦和支持可测试性的接口。IoC / DI容器(例如Castle Windsor,Unity)是可用于提供IoC的工具(库)。这些工具提供了超出简单依赖管理之外的扩展功能,包括生存期,AOP /拦截,策略等。

  2. 一个。减轻了负责管理组件依赖关系的组件的负担。
    b。提供在不同环境中交换依赖关系实现的功能。
    C。允许通过模拟依赖项来测试组件。
    d。提供一种在整个应用程序中共享资源的机制。

  3. 一个。在进行测试驱动的开发时至关重要。没有IoC,可能很难进行测试,因为被测组件与系统的其余部分高度耦合。
    b。开发模块化系统时至关重要。模块化系统是无需重新编译即可更换组件的系统。
    C。至关重要的是,在企业应用程序中尤其需要解决许多跨领域的问题。


2
实际上,IoC并不是主要用于管理依赖项。请参阅martinfowler.com/articles/injection.html#InversionOfControl特别要注意的部分是:“控制反转是一个笼统的术语,因此人们会感到困惑。因此,与各种IoC倡导者进行了大量讨论,我们以“依赖注入”为名。
罗杰里奥(Rogério)2010年

26

仅回答第一部分。它是什么?

控制反转(IoC)意味着先创建依赖项实例,然后再创建类的实例(可选地通过构造函数注入它们),而不是先创建该类的实例,然后再创建该类实例以创建依赖项实例。因此,控制的反转反转控制的流程的程序。代替被叫方控制控制的流程(在创建依赖关系),则主叫方控制程序的控制的流程



22

我将写下对这两个术语的简单理解:

For quick understanding just read examples*

依赖注入(DI):
依赖注入通常意味着将方法所依赖的对象作为参数传递给方法,而不是让方法创建依赖对象
实际上,这意味着该方法不直接取决于特定的实现。满足要求的任何实现都可以作为参数传递。

有了这个对象,就可以告诉他们依赖性。春天使它可用。
这导致松散耦合的应用程序开发。

Quick Example:EMPLOYEE OBJECT WHEN CREATED,
              IT WILL AUTOMATICALLY CREATE ADDRESS OBJECT
   (if address is defines as dependency by Employee object)

控件反转(IoC)容器:
这是框架的常见特征,IOC 管理Java对象
-从实例化到通过其BeanFactory进行销毁。
由IoC容器实例化的Java组件称为bean,而IoC容器管理bean的作用域,生命周期事件以及为其配置和编码的任何AOP功能

QUICK EXAMPLE:Inversion of Control is about getting freedom, more flexibility, and less dependency. When you are using a desktop computer, you are slaved (or say, controlled). You have to sit before a screen and look at it. Using keyboard to type and using mouse to navigate. And a bad written software can slave you even more. If you replaced your desktop with a laptop, then you somewhat inverted control. You can easily take it and move around. So now you can control where you are with your computer, instead of computer controlling it

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

将控制反转作为设计准则可达到以下目的:

某个任务的执行与实现之间存在脱钩。
每个模块都可以专注于其设计目的。
模块不假设其他系统在做什么,而是依赖其合同。
替换模块对其他模块没有副作用,
我将在这里保持抽象,您可以访问以下链接以详细了解该主题。
一个很好的例子

详细说明


17

我同意NilObject,但我想补充一点:

如果您发现自己复制了整个方法而只更改了一小段代码,则可以考虑通过控制反转解决它

如果你发现自己复制和粘贴周围的代码,你总是在做一些错误的。编成设计原则一次,也只有一次


17

例如,任务1是创建对象。没有IOC概念,任务#1应该由Programmer完成,但是有IOC概念,任务#1将由容器完成。

简而言之,控制权从程序员转换为容器。因此,这称为控制反转。

我在这里找到了一个很好的例子。


容器是IoC中的一个概念,对象模型(包括依赖项(“用户”对象与“已使用”对象之间的关系)和对象实例)驻留在对象模型中并受到管理(例如包含)。容器通常由IoC框架(例如Spring)提供。将其视为构成应用程序的对象的运行时存储库。
Awnry熊,2012年

17

可以说我们在某家酒店开会。

很多人,很多水,很多塑料杯。

当有人要喝酒时,她将杯子装满,喝水然后将杯子扔在地板上。

几个小时后,我们的地板覆盖了塑料杯和水。

让控制反转。

在同一地点的同一次会议,但是我们有一个带有一个玻璃杯的服务员,而不是塑料杯(Singleton)

她一直都在为客人喝酒

当有人要喝酒时,她从服务员玻璃杯里喝水,然后将其还给服务员。

抛开卫生的问题,最后一种饮酒过程控制更为有效和经济。

这正是Spring(另一个IoC容器,例如Guice)所做的。Spring IoC容器始终不让应用程序使用新关键字(用塑料杯)创建所需的东西,而是始终为应用程序提供所需对象(玻璃杯)的相同实例(单个)。

想想自己是这种会议的组织者。您需要向酒店管理部门传达以下信息:

会议成员将需要杯水,而不是小菜一碟。

例:-

public class MeetingMember {

    private GlassOfWater glassOfWater;

    ...

    public void setGlassOfWater(GlassOfWater glassOfWater){
        this.glassOfWater = glassOfWater;
    }
    //your glassOfWater object initialized and ready to use...
    //spring IoC  called setGlassOfWater method itself in order to
    //offer to meetingMember glassOfWater instance

}

有用的链接:


单例不是静态类型对象吗?
Gokigooooks,2015年

16

缩写“ IoC”和它所代表的名称似乎最令人困惑的地方是它的名称太迷人了-几乎是一个噪音名称。

我们真的需要一个名称来描述过程编程和事件驱动编程之间的区别吗?是的,如果需要的话,但是我们是否需要选择一个全新的“比生活更大”的名称,这个名称比解决方案更令人困惑?


1
IoC!=事件驱动。相似性(在某些情况下是重叠的),但它们基本上不是相同的范例。
Awnry熊,2012年

好问题。事件驱动的编程当然是IoC。我们编写事件处理程序,然后从事件循环中调用它们。但是,IoC比事件驱动的编程更具通用性。如果覆盖子类中的方法,它也是IoC的一种。您编写的代码将在使用适当的引用(实例)时被调用。
vi.su.

15

当您去杂货店并且妻子给您购买产品清单时,控制权就会被颠倒。

用编程的方式,她将回调函数传递给getProductList()您正在执行的函数- doShopping()

它允许函数的用户定义它的某些部分,使其更加灵活。


5
我妻子通常和我一起购物,但我同意这一说法。
ha9u63ar

1
@ ha9u63ar你的妻子和你一起逛街吗?那么,这就是所谓的聚合。
朱利安

2
如果她也给钱,那就叫做DI。

1
倒置(颠倒)一词来自于,当您的妻子打电话给getProductList()您时,您必须寻找金钱来源,这意味着控制权就在您身边。在倒挂的情况下,她将控制,也意味着她将提供购买的钱。

13

我在这里找到了一个非常清楚的示例该示例解释了“控制反转”的方式。

经典代码(无依赖注入)

这是不使用DI的代码大致起作用的方式:

  • 应用程序需要Foo(例如,控制器),因此:
  • 应用程序创建Foo
  • 应用程序调用Foo
    • Foo需要Bar(例如服务),因此:
    • Foo创建栏
    • Foo电话酒吧
      • Bar需要Bim(服务,存储库等),因此:
      • 栏创建Bim
      • 酒吧做什么

使用依赖注入

这是使用DI的代码大致起作用的方式:

  • 应用程序需要Foo,需要Bar,需要Bim,因此:
  • 应用程序创建Bim
  • 应用程序创建Bar并将其赋予Bim
  • 应用程序创建Foo并将其赋予Bar
  • 应用程序调用Foo
    • Foo电话酒吧
      • 酒吧做什么

依赖关系的控制从一个被调用转换为一个调用。

它解决什么问题?

依赖注入使与注入类的不同实现之间的交换变得容易。在进行单元测试时,您可以注入虚拟实现,这使测试变得更加容易。

例如:假设您的应用程序将用户上传的文件存储在Google云端硬盘中,那么您的控制器代码可能会以DI如下所示:

class SomeController
{
    private $storage;

    function __construct(StorageServiceInterface $storage)
    {
        $this->storage = $storage;
    }

    public function myFunction () 
    {
        return $this->storage->getFile($fileName);
    }
}

class GoogleDriveService implements StorageServiceInterface
{
    public function authenticate($user) {}
    public function putFile($file) {}
    public function getFile($file) {}
}

当您的要求发生变化时,要求您使用Dropbox代替GoogleDrive。您只需要为StorageServiceInterface编写一个保管箱实现。只要Dropbox实现遵循StorageServiceInterface,您就无需在控制器中进行任何更改。

在测试期间,您可以使用虚拟实现为StorageServiceInterface创建模拟,其中所有方法均返回null(或根据测试要求的任何预定义值)。

相反,如果您拥有控制器类来使用如下new关键字构造存储对象:

class SomeController
{
    private $storage;

    function __construct()
    {
        $this->storage = new GoogleDriveService();
    }

    public function myFunction () 
    {
        return $this->storage->getFile($fileName);
    }
}

如果您想通过Dropbox实现进行更改,则必须替换所有new构造GoogleDriveService对象的行,并使用DropboxService。除了在测试SomeController类时,构造函数始终希望使用GoogleDriveService类,并且该类的实际方法会被触发。

什么时候合适,什么时候不合适? 在我看来,当您认为某个类存在(或可能存在)替代实现时,可以使用DI。


这应该是最正确的答案,因为它唯一解释了“控制”是如何反转的。
Yarimadam

到目前为止最好的解释
Ali80

12

在这里可以找到一个非常简单的书面说明

http://binstock.blogspot.in/2008/01/excellent-explanation-of-dependency.html

它说 -

“任何非平凡的应用程序都是由两个或多个相互协作以执行某些业务逻辑的类组成。传统上,每个对象都负责获得对其协作对象的依赖关系(依赖关系)。当应用DI时,对象在创建时由一些外部实体赋予它们的依赖关系,这些外部实体协调系统中的每个对象。换句话说,依赖关系被注入到对象中。”


12

控制反转是一个通用原则,而依赖注入将这一原则实现为对象图构造的设计模式(即,配置控制对象如何相互引用,而不是对象本身控制如何获取对另一个对象的引用)。

将控制反转视为一种设计模式,我们需要查看正在反转的内容。依赖注入反转了构造对象图的控制。如果用通俗易懂的话讲,控制反转意味着程序中控制流的变化。例如。在传统的独立应用程序中,我们有一种主要方法,即将控件传递给其他第三方库的方法(以防万一,我们使用了第三方库的功能),但是通过反转控件,控件从第三方库的代码转移到了我们的代码中,因为我们正在使用第三方图书馆。但是程序中还有其他方面需要反转-例如,调用方法和线程以执行代码。

对于那些对控制反转有更深入的兴趣的人,已经发表了一篇论文,概述了控制反转作为设计模式的更完整图景(OfficeFloor:使用办公模式来改善软件设计http://doi.acm.org/10.1145/ 2739011.2739013,可从http://www.officefloor.net/about.html下载免费副本。

确定的是以下关系:

控制反转(用于方法)=依赖项(状态)注入+连续注入+线程注入

以上有关控制反转的关系的摘要-http: //dzone.com/articles/inversion-of-coupling-control


这是一个非常明确的答案。感谢您的解释。
蔡育瑜(

11

IoC是关于反转您的代码与第三方代码(库/框架)之间的关系的:

  • 在正常的软件开发中,您编写main()方法并调用“库”方法。 在控制中:)
  • 在IoC中,“框架”控制main()并调用您的方法。该框架处于控制之中:(

DI(依赖注入)与控件在应用程序中的流动方式有关。传统的桌面应用程序具有从您的应用程序(main()方法)到其他库方法调用的控制流,但是随着DI控制流的反转,该框架将负责启动您的应用程序,对其进行初始化并在需要时调用您的方法。

最后你总是赢:)


10

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

...


该链接非常有帮助。非常感谢:)
M FuatNUROĞLU19年

10

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

控制反转(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


1
我在这里找到最合适的答案。
blueray

8

编程口语

简单地说,IoC:这是使用接口作为通配符将某些特定事物(例如字段或参数)用作某些类可以使用的方式。它允许代码的重用性。

例如,假设我们有两个类:DogCat。两者具有相同的质量/状态:年龄,大小,体重。因此,而不是创造一类服务称为DogServiceCatService,我可以创建一个单一的一个叫AnimalService,只允许他们是否使用该接口使用狗和猫IAnimal

但是,从务实的角度来说,它有些落后。

a)大多数开发人员都不知道如何使用它。例如,我可以创建一个名为Customer的类,并且可以自动创建(使用IDE的工具)一个名为ICustomer的接口。因此,无论接口是否被重用,都很难找到一个充满类和接口的文件夹。它被称为BLOATED。有人可能会争辩说“也许将来我们可以使用它”。:-|

b)有一些限制。例如,让我们谈谈“ 猫”的情况,我想添加仅针对狗的新服务(功能)。假设我要计算训练狗的天数(trainDays()),因为猫没用,猫不能训练(我在开玩笑)。

b.1)如果我添加trainDays()到Service AnimalService,那么它也可以与cats一起使用,并且完全无效。

b.2)我可以在trainDays()其中评估使用哪个类的条件中添加一个条件。但是它将完全破坏IoC。

b.3)我可以创建一个称为DogService的新服务类为新功能的新。但是,这将增加代码的可维护性,因为我们将为Dog提供两类服务(具有相似的功能),这很不好。


关于肿的类/接口:您不必总是重复使用每个接口。有时,将大型接口拆分为许多较小的接口以了解其功能边界是很有意义的。较小的接口也更易于在其他实现中重用。它还鼓励您在任何有意义的地方编写接口代码。考虑“接口隔离”。仅因为您正在使用接口,并不意味着您已分离。单一的胖接口是没有用的。-只需我的2美分:)
MK

7

我已经阅读了很多答案,但是如果有人仍然感到困惑,并且需要一个额外的“ laymans术语”来解释IoC,这是我的看法:

想象一下父母和孩子互相交谈。

没有IoC:

*家长:您只有在我问您问题时才能讲话,并且只有在我允许您的情况下您才能采取行动。

父母:这就是说,如果我不问你,你不能问我是否可以吃饭,玩耍,上厕所甚至睡觉。

父母:你想吃吗?

小孩:没有

父母:好的,我会回来的。等等我。

孩子:(想玩,但由于没有父母的问题,孩子无法做任何事情)。

1小时后...

父母:我回来了。你想玩吗?

孩子:是的。

父级:已授予权限。

孩子:(最终可以玩)。

这个简单的场景说明了控件以父控件为中心。孩子的自由受到限制,在很大程度上取决于父母的问题。孩子只能在被要求说话时说话,并且只能在获得许可行动。

使用IoC:

现在,孩子可以提出问题了,父母可以以答案和权限进行回答。仅仅意味着控件是反向的!现在,孩子可以随时随地提问,尽管与父母之间在权限方面仍然存在依赖性,但他并不依赖于说话/问问题的方式。

用技术上的解释,这与console / shell / cmd vs GUI交互非常相似。(哪个是马克·哈里森的答案高于第二名的最高答案)。在控制台中,您依赖于所要/显示的内容,并且必须先回答问题才能跳到其他菜单和功能。遵循严格的顺序流程。(以编程方式,这就像一个方法/功能循环)。但是,使用GUI可以布置菜单和功能,并且用户可以选择所需的任何内容,从而具有更多的控制权和更少的限制。(以编程方式,选择菜单时会回调,并执行操作)。


6

控制反转是将控制权从库转移到客户端。当我们谈论一个将函数值(lambda表达式)注入(传递)到控制(更改)库函数行为的高阶函数(库函数)中的客户端时,这更有意义。将库依赖项(带有行为)注入库中的客户端或框架也可以视为IoC


5

既然已经有很多关于该问题的答案,但是没有一个答案能说明Inversion Control术语的细目分类,因此我看到了给出更简洁实用的答案的机会。

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

共有三种控制反转类型:

接口反转 提供程序不应定义接口。取而代之的是,消费者应该定义接口,提供者必须实现它。接口倒置使您无需在每次添加新的提供程序时修改使用者。

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

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


3
  1. 所以上面的第一什么是控制反转?

  2. 维护是它为我解决的第一件事。它保证了我正在使用接口,以便两个类彼此之间不亲密。

通过使用诸如温莎城堡的容器,它可以更好地解决维护问题。能够将进入数据库的组件换成使用基于文件的持久性的组件而无需更改代码行,这真是太棒了(配置更改已完成)。

一旦您使用了泛型,它就会变得更好。想象一下,有一个消息发布者可以接收记录并发布消息。它不在乎它发布什么,但是它需要一个映射器来从记录到消息中获取内容。

public class MessagePublisher<RECORD,MESSAGE>
{
    public MessagePublisher(IMapper<RECORD,MESSAGE> mapper,IRemoteEndpoint endPointToSendTo)
    {
      //setup
    }
}

我曾经写过它,但是现在,如果我发布不同类型的消息,则可以将许多类型注入到这组代码中。我也可以编写映射器来记录相同类型的记录并将它们映射到不同的消息。将DI与Generics结合使用使我能够编写很少的代码来完成许多任务。

哦,是的,有一些可测试性问题,但它们仅次于IoC / DI的好处。

我绝对喜欢IoC / DI。

3。当您拥有一个稍微复杂一些的中型项目时,它就变得更加合适。我想说,当您开始感到疼痛的那一刻就变得很合适。



3

在类内创建对象称为紧密耦合,Spring通过遵循设计模式(DI / IOC)消除了这种依赖性。在类的哪个对象中传入构造函数,而不是在类中创建。此外,我们在构造函数中提供超类引用变量以定义更通用的结构。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.