为什么控制反转以这种方式命名?


98

这些话invertcontrol根本不使用在我见过的定义来定义控制反转。

定义

维基百科

控制反转(IoC)是一种编程技术,在此以面向对象编程的形式表示,其中对象耦合在运行时由汇编程序对象绑定,通常在编译时使用静态分析是未知的。〜http://en.wikipedia.org/wiki/Inversion_of_control

马丁·福勒

控制反转是Java社区中的一种常见模式,可帮助连接轻量级容器或将来自不同项目的组件组装到一个内聚的应用程序中。〜基于 http://www.martinfowler.com/articles/injection.html (改写)


那么,为什么将控制反转称为控制反转?什么控制被颠倒了?有没有一种方法可以使用术语“ 反转控制”来定义“控制反转”


22
可以用简单的英语
罗伯特·哈维

2
如果一个定义使用了它所定义的词,那将是字典的失败。类似的逻辑在这里适用。
2013年

2
另请参见StackOverflow:什么是控制反转?
2013年

2
@Izkata,字典中通常用短语自己的术语或同义词来定义短语。即:自然力定义在其定义中使用力和自然一词,或者服务术语在规则与术语同义的情况下使用规则和服务。
Korey Hinton

3
由于这个原因,我更喜欢使用术语“自动依赖项注入”或ADI而不是IoC。福勒本人提到,当前大多数编程范例都具有某种“控制反转”,其中该术语通常被定义为“将要执行的代码的确定和应将其执行的时间放回外部”。传统上由程序本身做出这样的确定”。从硬件中断到事件驱动的编程,再到虚拟机和多任务,一切都在IoC中进行。
KeithS

Answers:


67

假设您有某种“存储库”类,该存储库负责将数据从数据源传递给您。

存储库可以自己建立与数据源的连接。但是,如果它允许您通过存储库的构造函数传递到数据源的连接怎么办?

通过允许调用者提供连接,您已将数据源连接依赖项与存储库类解耦,从而允许任何数据源都可以与存储库一起使用,而不仅仅是存储库指定的数据源。

您已经将创建从存储库类到调用者的连接的责任交给了反向控制

Martin Fowler建议使用术语“依赖注入”来描述这种类型的控制反转,因为控制反转作为一个概念可以应用得比在构造函数方法中注入依赖更为广泛。


3
这是依赖注入的一个示例,但是依赖注入只是控制反转的一个子集。还有其他与依赖注入完全无关的东西,它们会实现控制反转。
dsw88 2013年

1
@ mustang2009cobra:谢谢。我所追求的只是一个例子,而不是发生它的实例的完整目录,术语“控制反转”与DI密切相关,而不是消息传递或其他类似概念。
罗伯特·哈维

的确,依赖注入是IoC的最常见示例,因此DI的示例最有意义。也许对您的答案进行了很小的更改,以指定这是DI示例,这是IoC的最突出类型?
dsw88

1
这正是我想要的!IoC的许多示例和定义都没有明确指出要反转哪个控件。我意识到IoC不仅有依赖注入,还可以提供更多的东西,但是现在终于有一些事情在我脑海中震撼了。谢谢!
Korey Hinton

79

我认为没有人能比马丁·福勒(Martin Fowler)更好地解释它,在您所链接的文章的后面

对于这种新型容器,反转是关于它们如何查找插件实现的。在我的幼稚示例中,列表器通过直接实例化查找器实现。这将阻止查找程序成为插件。这些容器使用的方法是确保插件的任何用户都遵循某种约定,该约定允许单独的汇编器模块将实现注入到列表器中。

正如他在以上段落中所解释的那样,这与“控制反转”这个术语的产生原因并不完全相同。

当这些容器谈论它们因实现“控制反转”而如此有用时,我最终感到非常困惑。控制反转是框架的共同特征,因此说这些轻量级容器之所以特别是因为它们使用控制反转,就好像说我的汽车很特别,因为它带有轮子。

问题是,它们在控制的哪个方面倒置?当我第一次遇到控件反转时,它位于用户界面的主控件中。早期的用户界面由应用程序控制。您将有一系列命令,例如“输入名称”,“输入地址”;您的程序将驱动提示并获取对每个提示的响应。使用图形(甚至基于屏幕)UI时,UI框架将包含此主循环,而您的程序将为屏幕上的各个字段提供事件处理程序。程序的主控件被反转,从您移至框架。

这就是为什么他继续创造术语“依赖注入”以涵盖控制反转的这种特定实现的原因。

因此,我认为我们需要为该模式指定一个更具体的名称。控制反转过于笼统,因此人们会感到困惑。经过与各种IoC倡导者的大量讨论,我们决定使用依赖注入这个名称。

澄清一下:控制反转是指使程序的控制结构与经典程序设计相反的任何内容。

在过去的日子里,一个关键的例子就是让框架处理UI与您的代码之间的通信,而不是让您的代码直接生成UI。

在最近的时代(当这样的框架几乎占主导地位时,这个问题就不再适用了),一个例子就是反转对对象实例化的控制。

Fowler等人认为“控制反转”一词涵盖了太多的技术,我们需要一个新的术语来表示对象实例化的特定示例(“依赖注入”),但是在达成协议时,“ IoC容器”一词”起飞了。

因为IoC容器是一种特定类型的依赖注入,但是依赖注入是控制反转的一种特定类型,这使水非常混乱。这就是为什么无论您身在何处都得到如此困惑的答案的原因。


4
我通常不会投票那么多,但这是一个很好的答案。1票还不够。:(
RJD22

1
这个答案确实回答了IoC和DI混淆的所有问题。
Ali Umair

32

这是通常遵循的“常规”控制流程程序:

  • 顺序运行命令
  • 您可以控制程序的控制流程

Control Inversion会“反转”控制流,这意味着它会将其翻转:

  • 您的程序不再控制流程了。您不必等待别人认为合适的命令,而要等别人叫您

最后一行很重要。而不是在您喜欢时给别人打电话,而是在别人喜欢时给您打电话。

一个常见的例子是Web框架,例如Rails。您定义了控制器,但实际上并没有决定何时调用它们。Rails在确定需要时会调用它们。


19
[在此处插入强制性的“苏联”笑话]
罗伯特·哈维

2
顺便说一句,我认为您所描述的更像是消息传递,几十年来,消息传递一直是面向对象的。
罗伯特·哈维

3
@JimmyHoffa-一点都没错-参见这里。控制反转是比创建依赖项更笼统的概念,并且依赖项注入只是IoC的一种形式。

7
@JimmyHoffa:我不同意。这是对控制反转的正确定义,就像您的答案或罗伯特的定义一样,这正是Fowler在创造短语Dependency Injection(这就是你们都称为IoC)时所描述的困惑。
pdr

5
我同意@pdr的说法:“控制反转意味着从经典的程序设计中反转程序的控制结构的任何事情。” 这就是我要描述的,并且是控制的通用反转。依赖注入是IoC的一种,但这并不是唯一实践IoC的东西
dsw88 2013年

13

它与谁控制依赖项的实例化有关。

传统上,当一个类/方法需要使用另一个类(依赖关系)时,将由该类/方法直接实例化。它控制着它的依赖关系。

使用控制反转(IoC),调用方传入了依赖关系,因此实例化了依赖关系。调用者控制依赖关系。

实例化依赖关系的控件已被反转-而不是在“底部”(需要代码的位置),而是在“顶部”(需要调用代码的位置)实例化该控件。


1
不懂英语。失败。
罗伯特·哈维

2
@RobertHarvey-平原有多平坦?
Oded

4
@RobertHarvey嗯?对我来说,这似乎很简单……什么不简单?单词“实例化”还是“依赖项”?
吉米·霍法

@JimmyHoffa:请参阅我的答案,其中提供了一个具体示例。IoC是每个人都使用但没有人解释的那些麻烦术语之一;人们在不需要它们的程序中使用IoC容器,因为它们会误解。就是说,我认为这个答案有几个忍者编辑。:)
罗伯特·哈维

这和上面@ dsw88的帖子对我来说很简单。看,如果我从未听说过IoC,我会直觉地认为“哦,谁来控制某物从一侧翻转到另一侧”。做得好!
奥利弗·威廉姆斯

2

通常,较高级别的代码调用(即控件)较低级别的代码。Main()调用function(),function()调用libraryFunction()。

可以将其反转,因此底部的低级库函数将调用较高级的函数。

为什么要这么做?中间件。有时您想控制顶层和底层,但是中间却有很多工作是您不想做的。以C stdlib中的quicksort的实现为例。您在顶层调用quicksort。您将qsort()的函数指针传递给您自己的函数,该函数根据您的喜好实现比较器。调用qsort()时,它将调用此比较器函数。qsort()正在控制/调用/驱动您的高级函数。


1

这是一个简单的概述:

  1. 控制是指程序下一步要做的事情
  2. 在顶层,通常有两件事可以控制控件:应用程序本身和用户

在过去,控件首先由应用程序拥有,而用户则由用户拥有。如果应用程序需要用户提供某些东西,它将停止并询问,然后继续执行下一个任务。用户的交互主要提供数据,而不是控制应用程序下一步做什么。如今,这对我们来说有点陌生,因为我们很少看到这种行为。

如果我们将其切换并给用户主要控制权,那么我们已经倒置了该控制权。这意味着,用户无需等待应用程序给它做某事,而是坐在周围等待用户给它做某事。GUI就是一个很好的例子,带有事件循环的几乎所有东西都具有反向控件。

请注意,我的示例在最高级别,并且这种控制反转的概念可以抽象到应用程序内不同的控制层(即依赖项注入)。这可能就是为什么很难获得直接答案的原因。


0

让我们尝试通过两个示例来理解它。

例子1

在早期,用于生成命令提示符的应用程序接连接受用户输入。如今,UI框架实例化了各种UI元素,遍历了这些UI元素的各种事件(例如,鼠标悬停,单击等),并且用户/主程序提供了用于监听这些事件的钩子(例如Java中的UI事件监听器)。因此,主要的UI元素流“控件”已从用户程序移至UI框架。在早期,它在用户程序中。

例子2

考虑CustomerProcessor以下课程:

class CustomerProcessor
{
    SqlCustRepo custRepo = new SqlCustRepo(); 
    private void processCustomers()
    {
            Customers[] custs = custRepo.getAllCusts();
    }
}

如果我想processCustomer()独立于的任何实现getAllCusts(),而不仅仅是所提供的实现SqlCustRepo,我将需要摆脱限制:SqlCustRepo custRepo = new SqlCustRepo()并用更通用的,可以接受各种实现类型的东西来代替它,这样该代码processCustomers()将仅适用于任何提供的实现。上面的代码(SqlCustRepo通过主程序逻辑实例化所需的类)是一种传统方法,并且无法实现processCustomers()与实现分离的目标getAllCusts()。在控制反转中,容器实例化所需的实现类(如xml配置所指定),将其注入到主程序逻辑中,该逻辑按指定的钩子进行绑定(例如通过spring框架中的@Autowired注释或 getBean()方法)。

让我们看看如何做到这一点。考虑下面的代码。

Config.xml

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
    <bean id="custRepo" class="JsonCustRepo" />
</beans>

CustRepo.java

interface ICustRepo 
{ ... }

JsonCustRepo.java

class JsonCustRepo implements CustRepo
{ ... }

App.java

class App
{
    public static void main(String[] args) 
    {
        ApplicationContext context = new ClassPathXmlApplicationContext("Config.xml");
        ICustRepo custRepo = (JsonCustRepo) context.getBean("custRepo");
    }
}

我们也可以

class GraphCustRepo implements ICustRepo { ... }   

<bean id="custRepo" class="GraphCustRepo">

并且我们将不需要更改App.java。

容器(这是Spring框架)上方负责扫描xml文件,实例化特定类型的bean并将其注入用户程序。用户程序无法控制要实例化哪个类。

PS:IoC是通用概念,可以通过多种方式实现。上面的示例通过依赖项注入来实现。

参考: Martin Fowler的文章

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.