ChatGPT解决这个技术问题 Extra ChatGPT

什么是控制反转?

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

它是什么?它解决了哪个问题?什么时候适合使用,什么时候不适合?

大多数这些答案的问题是使用的术语。什么是容器?反转?依赖?用通俗的话解释一下,不用大词。
另请参阅 Programmers.SE:Why is Inversion of Control named that way?
它是依赖注入 (DI) - 请参阅 Martin Fowlers 的描述:martinfowler.com/articles/injection.html#InversionOfControl
它是形容词,不是名词,不是事物,它是对代码更改的描述,其中流的控制在委托中,而不是容器中。
了解 IOC 的最佳方式是听 Martin Fowler 和 BOB 叔叔……只需输入 IOC 并输入提及的名称

T
Top-Master

Inversion-of-Control (IoC) 模式是关于提供任何种类callback(控制反应),而不是直接行动(换句话说,倒置和/或将控制重定向到外部处理程序/控制器)。 Dependency-Injection (DI) 模式是 IoC 模式的一个更具体的版本,旨在从代码中移除依赖项。

每个 DI 实现都可以被认为是 IoC,但不应将其称为 IoC,因为实现依赖注入比回调更难(不要使用通用术语“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;
    }
}

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

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

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

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


好清晰的例子。但是,假设我们不需要将 ISpellChecker 接口传递给对象的构造函数,而是将其作为可设置属性(或 SetSpellChecker 方法)公开。这仍然会构成 IoC 吗?
chainguy1337 - 是的。使用这样的设置器称为设置器注入,而不是构造器注入(两种依赖注入技术)。 IoC 是一种相当通用的模式,但依赖注入实现了 IoC
我同意@Rogeria。这并不能解释为什么它被称为国际奥委会,我对赞成票的数量感到惊讶;-)
我支持@Rogerio 和@Pangea。这可能是 constructor injection 的一个很好的例子,但不是原始问题的一个很好的答案。由 Fowler 定义的 IoC 可以在不使用任何类型的注入的情况下实现,例如通过使用 service locator 甚至简单的继承。
您给出的示例不是 IOC,而是依赖注入示例。
H
Hearen

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

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

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


不要标记这个人。从技术上讲,他是正确的 martinfowler.com/bliki/InversionOfControl.html IoC 是一个非常普遍的原则。控制流被依赖注入“反转”,因为你已经有效地将依赖委托给了一些外部系统(例如 IoC 容器)
同意施耐德的意见。 5 反对票?令人难以置信,因为这是唯一真正正确的答案。注意开头:'就像一个 gui 程序。依赖注入只是 IoC 最常见的实现。
确实,这是为数不多的正确答案之一!伙计们,IoC 从根本上讲不是依赖关系。一点也不。
+1 - 这是 Martin Fowler 对以下语句的一个很好的描述(带有示例) - “早期的用户界面由应用程序控制。您将有一系列命令,例如“输入名称”、“输入地址”;您的程序将驱动提示并对每个提示进行响应。对于图形(甚至基于屏幕)的 UI,UI 框架将包含此主循环,而您的程序则为屏幕上的各个字段提供事件处理程序。程序被颠倒了,从你那里移到了框架。”
我现在明白为什么它有时被戏称为“好莱坞原则:不要打电话给我们,我们会打电话给你”
r
rpattabi

什么是控制反转?

如果您遵循这两个简单的步骤,您就完成了控制反转:

将“做什么”部分与“何时做什么”部分分开。确保当零件尽可能少地知道什么零件;反之亦然。

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

--

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

--

例子

事件处理。事件处理程序(待办事项部分)——引发事件(何时待办事项部分)

依赖注入。构建依赖项(what-to-do 部分)的代码——在需要时为客户端实例化和注入该依赖项,这通常由 Dagger 等 DI 工具(when-to-do-part)处理。

接口。组件客户端(when-to-do部分)——组件接口实现(what-to-do部分)

xUnit 夹具。 Setup 和 TearDown(待办事项部分)——xUnit 框架在开头调用 Setup 并在结尾调用 TearDown(待办事项部分)

模板方法设计模式。模板方法when-to-do部分——原始子类实现what-to-do部分

COM 中的 DLL 容器方法。 DllMain、DllCanUnload 等(待办事项部分)——COM/OS(待办事项部分)


你怎么说接口。当我们使用接口(例如:依赖注入)时,组件客户端(何时执行部分)作为“何时”没有意义,我们只是将其抽象出来并为客户端提供添加任何实现的灵活性,但没有“何时”参与其中。在引发事件处理事件的情况下,我同意“何时”。
“组件客户端”是指界面的用户/客户端。客户端知道“何时”触发“做什么”部分,无论其意图是否是扩展功能。
看看 Martin Fowler 的这篇精彩文章。他展示了接口如何构成控制反转的基本部分:martinfowler.com/articles/injection.html#InversionOfControl
前两句很精彩。惊人的 !!完美区分何时做和做什么!我不知道为什么其他答案会得到这么多的赞成票。他们只是在没有任何理解的情况下谈论代码。
@rpattabi,举一个你在答案中所说的代码示例
B
Bharat Mallapur

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

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

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

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


@Luo Jiong Hui 很好的解释。
大多数设计模式,如果不是全部的话,在我们的日常生活中都有它们的对应物,我们可以很好地看到和理解。理解设计模式最有效的方法是了解日常生活中的对应物。我相信有很多。
错误的答案。您正在解释 依赖注入 而不是 IoC。请参阅 Rogério 对 this answer above 的评论
我同意。这是 DI,不是 IoC。仍然得到赞成,因为它是一种简单的方法,但有助于扩大对该主题的理解。
它解释了依赖注入。不是国际奥委会。但是很好而清晰的解释。
i
iliketocode

控制反转(或 IoC)是关于获得自由(你结婚了,你失去了自由,你被控制了。你离婚了,你刚刚实施了控制反转。这就是我们所说的“解耦”。好的计算机系统不鼓励一些非常亲密的关系。)更多的灵活性(你办公室的厨房只供应干净的自来水,这是你想喝的唯一选择。你的老板通过设置一台新咖啡机实施了控制反转。现在你得到了选择自来水或咖啡的灵活性。)和更少的依赖(你的伴侣有工作,你没有工作,你在经济上依赖你的伴侣,所以你被控制了。你找到了工作,你已经实施了控制。好的计算机系统鼓励独立。)

当您使用台式计算机时,您已经从属(或者说,被控制)。你必须坐在屏幕前看着它。使用键盘键入并使用鼠标导航。一个写得不好的软件会更加奴役你。如果你用笔记本电脑代替你的台式机,那么你的控制就有点颠倒了。您可以轻松携带它并四处走动。因此,现在您可以使用计算机控制您所在的位置,而不是您的计算机控制它。

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

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

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

尽管工作人员将顶层项目管理的控制权交给了管理者(框架)。但是最好有一些专业人士的帮助。这就是IoC的概念真正的来历。

具有 MVC 架构的现代 Web 应用程序依赖于框架来执行 URL 路由并将控制器放置在适当的位置以供框架调用。

依赖注入和控制反转是相关的。依赖注入是微观层面的,控制反转是宏观层面的。您必须吃完每一口食物(实施 DI)才能完成一顿饭(实施 IoC)。


我投票赞成将 DI 与婚姻进行比较,将 IoC 与离婚进行比较。
“虽然工人将管理项目的顶层控制权交给了管理者(框架)。但是有一些专业人士的帮助是很好的。这才是真正的 IoC 概念。” - 首先是控制权与经理。你能解释一下这个控制是如何倒置的吗?在专业人士的帮助下(什么样的专业人士)?如何 ?
@Istiaque Ahmed,与工人完全控制一切的车库相比,现代汽车工厂的经理控制着生产。所以现在工人被控制而不是控制。请注意,在这种情况下,经理是现代汽车工厂的一部分,而不是工人的一部分。专业人士是在规划和制造汽车方面专业的管理人员。
给已婚人士的信息:现在不要离婚,你的孩子班也可能实施IoC。
是的,这只是我对婚姻的看法,也是 IoC
E
Efe Ariaroo

在使用控制反转之前,您应该清楚地知道它有其优点和缺点,并且如果您这样做,您应该知道为什么要使用它。

优点:

您的代码被解耦,因此您可以轻松地将接口的实现与替代实现交换

它是针对接口而不是实现进行编码的强大动力

为您的代码编写单元测试非常容易,因为它只依赖于它在其构造函数/设置器中接受的对象,并且您可以轻松地用正确的对象单独初始化它们。

缺点:

IoC 不仅会反转程序中的控制流,还会使程序变得混乱。这意味着您不能再仅仅阅读您的代码并从一个地方跳到另一个地方,因为通常在您的代码中的连接不再在代码中。相反,它位于 XML 配置文件或注释以及解释这些元数据的 IoC 容器的代码中。

出现了一类新的错误,您的 XML 配置或注释错误,您可能会花费大量时间找出为什么您的 IoC 容器在某些条件下将空引用注入您的对象之一。

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


我认为他的意思是,你不能只阅读:myService.DoSomething() 并转到 DoSomething 的定义,因为在 IoC 中,myService 只是一个接口,你不知道实际的实现,除非你去看它在 xml 配置文件或设置 ioc 的主要方法中。
这就是 Resharper 提供帮助的地方 - 针对界面“单击转到实现”。避免 IoC(或者更具体地说是您的示例中的 DI)可能也意味着您没有正确测试
回复:它把你的软件变成了一个类的集合,这些类不再构成一个“真正的”程序,而只是需要通过 XML 配置或注释元数据组合在一起的东西,没有它就会崩溃(和崩溃)——我认为这个非常具有误导性。对于在框架之上编写的任何程序都可以这样说。一个好的 IoC 容器的不同之处在于,如果您的程序设计和编写良好,您应该能够将其取出并放入另一个容器中,而对代码的更改最少,或者完全抛弃 IoC 并手动构建您的对象.
很高兴看到这样的真实答案!我认为有很多经验丰富的程序员,熟悉面向对象的设计和 TDD 实践,在“IoC”这个流行词被发明之前就已经在使用接口、工厂模式、事件驱动模型和模拟了。不幸的是,如果您不使用他们喜欢的框架,太多的开发人员/“架构师”声称这是一种不好的做法。我更喜欢更好的设计,使用内置的语言概念和工具,以复杂性的一小部分实现相同的目标,即没有像你说的那样“混淆”实现:-)
您可以在没有接口、注释或任何框架支持的情况下进行依赖注入。至少,您所需要的只是类,这些类具有构造函数,这些构造函数采用它们需要的其他对象的实例。最大的不同是你不允许这些类自己创建东西。一个简单的 main 方法可以扮演依赖注入框架的角色,并简单地在每个对象上调用 new。编写这个主要方法很乏味。但是您的其余代码保持可读性和清洁性,并且将管道放在一个地方实际上也很好。
C
Community

维基百科文章。对我来说,控制反转是把你顺序编写的代码变成一个委托结构。您的程序不是显式地控制一切,而是设置一个类或库,其中包含在某些事情发生时要调用的某些函数。它解决了代码重复。例如,在过去,您会手动编写自己的事件循环,轮询系统库以获取新事件。如今,大多数现代 API 您只需告诉系统库您感兴趣的事件,它就会让您知道它们何时发生。控制反转是减少代码重复的实用方法,如果您发现自己复制了整个方法而只更改了一小部分代码,您可以考虑使用控制反转来解决它。在许多语言中,通过委托、接口甚至原始函数指针的概念,控制反转很容易。它并不适合在所有情况下使用,因为以这种方式编写的程序流程可能更难遵循。在编写将被重用的库时,这是一种设计方法的有用方法,但除非它真正解决了代码重复问题,否则应该在您自己程序的核心中谨慎使用。


我发现维基百科的文章非常混乱,需要修复。查看讨论页面以获取笑声。
编写自己的事件循环仍然可能是控制反转,如果该事件循环代替框架并且其余代码使用 IoC 原则,那么您刚刚编写了自己的框架。这实际上不是一件坏事,它增加了可读性,但只需要多一点编码(也不是总是合适的)。
L
Luo Jiong Hui

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

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

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

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


M
Michal Sznajder

但我认为你必须非常小心。如果你过度使用这种模式,你会做出非常复杂的设计甚至更复杂的代码。

就像在这个 TextEditor 的例子中一样:如果你只有一个 SpellChecker,也许真的没有必要使用 IoC 吗?除非你需要写单元测试什么的……

总之:讲道理。设计模式是好的实践,但不是要传讲的圣经。不要到处粘。


你怎么知道你只有一个拼写检查器?
@Trace 例如,您可以知道要编写的程序将如何使用。依赖注入等一些技术仍然非常便宜,以至于在这种情况下很少有理由不使用它们。
f
ferventcoder

对我来说,IoC / DI 将依赖关系推向调用对象。超级简单。

非技术性的答案是能够在您打开汽车之前更换汽车中的发动机。如果一切都连接正确(界面),那么你很好。


u
user2330678

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

控制反转 (IoC) 意味着先创建依赖项实例,然后创建类的后一个实例(可选地通过构造函数注入它们),而不是先创建类实例,然后类实例创建依赖项实例。因此,控制反转会反转程序的控制流程。调用者控制程序的控制流,而不是被调用者控制控制流(同时创建依赖项)。


它与类或对象创建无关,请检查此martinfowler.com/articles/injection.html#InversionOfControl
在我 15 年的编程生涯中,这是迄今为止对 IoC 是什么的最清晰的解释。谢谢!主要的答案并没有说它是关于类或对象的创建,恕我直言。它说明了使用创建类作为示例的控制反转。 :) 作者使用最少的句子和单词来传达答案!恕我直言,这个答案是本文中最好的答案。为什么它是最后被发现的条目之一......
这是一个很好的解释。谢谢
M
Michał Powaga

控制反转是一种用于解耦系统中的组件和层的模式。该模式是通过在构建组件时将依赖项注入组件来实现的。这些依赖关系通常作为接口提供,用于进一步解耦和支持可测试性。 IoC / DI 容器,例如 Castle Windsor、Unity 是可用于提供 IoC 的工具(库)。这些工具提供了超越简单依赖管理的扩展功能,包括生命周期、AOP/拦截、策略等。减轻组件负责管理其依赖项的责任。湾。提供在不同环境中交换依赖项实现的能力。 C。允许通过模拟依赖项来测试组件。 d。提供一种在整个应用程序中共享资源的机制。一个。在进行测试驱动开发时至关重要。如果没有 IoC,可能很难进行测试,因为被测组件与系统的其余部分高度耦合。湾。开发模块化系统时至关重要。模块化系统是其组件无需重新编译即可更换的系统。 C。如果有许多需要解决的横切关注点,尤其是在企业应用程序中,则至关重要。


实际上,IoC 主要不是关于管理依赖关系。请参阅martinfowler.com/articles/injection.html#InversionOfControl,特别要注意“控制反转这个术语过于笼统,因此人们会感到困惑。因此,在与各种 IoC 倡导者进行大量讨论后,我们决定将其命名为依赖注入”。
M
Mangu Singh Rajpurohit

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

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 通过其 BeanFactory 管理 Java 对象——从实例化到销毁。 - 由 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

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

控制反转作为设计指南的目的如下:

特定任务的执行与实现是脱钩的。
每个模块都可以专注于它的设计目的。
模块不对其他系统做什么做任何假设,而是依赖于它们的合同。
替换模块对其他模块没有副作用
我将在这里保持抽象,您可以访问以下链接以详细了解该主题。
A good read with example

Detailed explanation


S
SacredGeometry

假设我们在酒店开会。

我们邀请了很多人,所以我们遗漏了许多水壶和许多塑料杯。

当有人想喝水时,他/她装满一个杯子,喝完水,然后把杯子扔在地板上。

大约一个小时后,我们的地板上铺满了塑料杯和水。

让我们在反转控件后尝试:

想象一下在同一个地方举行同样的会议,但我们现在有一个服务员只拿着一个玻璃杯而不是塑料杯(单身)

当有人想喝酒时,服务员会给他们一杯。他们喝了它,然后把它还给服务员。

撇开卫生问题不谈,使用服务员(过程控制)更加有效和经济。

这正是 Spring(另一个 IoC 容器,例如:Guice)所做的。 Spring IoC 不是让应用程序使用 new 关键字创建它需要的东西(即拿一个塑料杯),而是为应用程序提供所需对象(一杯水)的相同杯子/实例(单件)。

把自己想象成这样一个会议的组织者:

例子:-

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

}

有用的链接:-

http://adfjsf.blogspot.in/2008/05/inversion-of-control.html

http://martinfowler.com/articles/injection.html

http://www.shawn-barrett.com/blog/post/Tip-of-the-day-e28093-Inversion-Of-Control.aspx


单例不是静态类型对象吗?
您解释了依赖注入,它与控制反转不同。依赖注入容器是控制反转的一种实现。
R
Raghavendra N

我找到了一个非常清晰的示例 here,它解释了“控件是如何倒置的”。

经典代码(无依赖注入)

以下是不使用 DI 的代码大致如何工作:

应用程序需要 Foo(例如控制器),所以:

应用程序创建 Foo

应用程序调用 Foo Foo 需要 Bar(例如服务),所以:Foo 创建 Bar Foo 调用 Bar Bar 需要 Bim(服务、存储库,...),所以:Bar 创建 Bim

Foo 需要 Bar(例如服务),所以:

Foo 创建 Bar

Foo 调用 Bar Bar 需要 Bim(服务、存储库……),所以: Bar 创建 Bim Bar 做某事

Bar 需要 Bim(服务、存储库……),所以:

Bar 创建 Bim

酒吧做点什么

使用依赖注入

以下是使用 DI 的代码大致如何工作:

应用需要Foo,需要Bar,需要Bim,所以:

应用程序创建 Bim

应用程序创建 Bar 并为其提供 Bim

应用程序创建 Foo 并给它 Bar

应用程序调用 Foo Foo 调用 Bar Bar 做某事

Foo 呼叫 Bar Bar 做某事

酒吧做点什么

依赖关系的控制从一个被调用反转到一个调用。

它解决了哪些问题?

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

例如:假设您的应用程序将用户上传的文件存储在 Google Drive 中,使用 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 实现。只要 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。


这应该是最正确的答案,因为它唯一解释了“控制”是如何倒置的。
迄今为止最好的解释
C
Community

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

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

如果您发现自己在复制和粘贴代码,那么您几乎总是在做错某事。编纂为设计原则Once and Only Once


g
gogs

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

简而言之,控制从程序员反转到容器。因此,它被称为控制反转。

我找到了一个很好的例子 here


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

似乎“IoC”这个首字母缩写词和它所代表的名字最令人困惑的是它的名字太迷人了——几乎是一个噪音名字。

我们真的需要一个名称来描述过程驱动编程和事件驱动编程之间的区别吗?好的,如果我们需要,但我们是否需要选择一个全新的“比生命更重要”的名称,它让人困惑多于解决问题?


IoC != 事件驱动。相似之处(并且在某些情况下重叠),但它们基本上不是相同的范式。
好问题。事件驱动编程当然是 IoC。我们编写事件处理程序并从事件循环中调用它们。但是,IoC 是比事件驱动编程更通用的概念。如果您在子类中覆盖一个方法,它也是一种 IoC。您编写的代码会在使用适当的引用(实例)时被调用。
P
Pang

控制反转是当你去杂货店时,你的妻子给了你要购买的产品清单。

在编程术语中,她将回调函数 getProductList() 传递给您正在执行的函数 - doShopping()

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


我的妻子通常和我一起购物,但我同意这种说法。
@ha9u63ar 你的妻子和你一起购物吗?那么这就是所谓的聚合。
如果她也给钱,那就叫 DI。
Inversion这个词-颠倒-来自,当你的妻子叫getProductList()你必须找到钱的来源时,意味着控制在你身边。在反转的情况下,她将控制,意味着她将提供购买的钱。
A
Abdullah Al Farooq

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

控制反转 (IoC) 建立在一个非常简单的原则之上,称为好莱坞原则。它说,

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

这意味着不要去好莱坞实现你的梦想,如果你值得,那么好莱坞会找到你并让你的梦想成真。几乎是颠倒的,对吧?

现在,当我们讨论 IoC 的原理时,我们常常忘记好莱坞。对于国际奥委会来说,必须具备三个要素,一个好莱坞,一个你和一个像实现你的梦想的任务。

在我们的编程世界中,好莱坞代表一个通用框架(可能由您或其他人编写),您代表您编写的用户代码,任务代表您想用代码完成的事情。现在你永远不会自己去触发你的任务,而不是在 IoC 中!相反,您已经设计了所有内容,以便您的框架将为您触发您的任务。因此,您构建了一个可重用的框架,可以使某人成为英雄或使另一个人成为恶棍。但该框架始终负责,它知道何时选择某人,而某人只知道它想成为什么。

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

然后你在你的框架中留下一些钩子,你可以在其中放置更多代码来生成自定义菜单、页面、cookies或记录一些用户事件等。在每个浏览器请求上,你的框架将运行并执行你的自定义代码,如果钩子然后返回它到浏览器。

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

Laravel 和 EJB 就是这种框架的例子。

参考:

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

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


我在这里找到的最合适的答案。
D
Daniel Sagenschneider

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

将控制反转视为一种设计模式,我们需要看看我们正在反转什么。依赖注入反转了对构建对象图的控制。如果用外行的话来说,控制反转意味着程序中控制流的变化。例如。在传统的独立应用程序中,我们有 main 方法,控制从那里传递给其他第三方库(如果我们使用了第三方库的函数),但是通过控制反转控制从第三方库代码转移到我们的代码,因为我们正在接受第三方图书馆的服务。但是在程序中还有其他方面需要反转——例如调用方法和线程来执行代码。

对于那些对控制反转更深入感兴趣的人,已经发表了一篇论文,概述了作为一种设计模式的控制反转的更完整图片(OfficeFloor:使用办公模式改进软件设计http://doi.acm.org/10.1145/2739011.2739013,可从 { 2})。

确定的是以下关系:

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

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


这是一个非常明确的答案。感谢您的解释。
D
DDan

我喜欢这个解释:http://joelabrahamsson.com/inversion-of-control-an-introduction-with-examples-in-net/

它开始很简单,并显示代码示例。

https://i.stack.imgur.com/H4Tbn.png

消费者 X 需要消费类 Y 来完成某事。这一切都很好而且很自然,但是 X 真的需要知道它使用了 Y 吗?

X 知道它使用了具有 Y 的行为、方法、属性等的东西而不知道谁实际实现了该行为,这还不够吗?

通过提取 X 在 Y 中使用的行为的抽象定义,如下图 I 所示,并让消费者 X 使用该行为的一个实例而不是 Y,它可以继续做它所做的事情,而无需了解有关 Y 的细节。

https://i.stack.imgur.com/i4x6r.png

在上面的插图中,Y 实现了 I,而 X 使用了 I 的一个实例。虽然 X 很可能仍然使用 Y,但有趣的是 X 并不知道这一点。它只知道它使用了实现 I 的东西。

阅读文章以获取更多信息和好处描述,例如:

X 不再依赖于 Y

更灵活,实现可以在运行时决定

代码单元隔离,测试更轻松

...


该链接非常有帮助。真的谢谢 :)
i
iliketocode

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

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

它说 -

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


M
Mangu Singh Rajpurohit

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

在正常的软件开发中,您编写 main() 方法并调用“库”方法。你在控制:)

在 IoC 中,“框架”控制 main() 并调用您的方法。框架在控制中:(

DI(依赖注入)是关于控制如何在应用程序中流动的。传统的桌面应用程序具有从您的应用程序(main() 方法)到其他库方法调用的控制流,但是通过 DI 控制流反转,框架负责启动您的应用程序、初始化它并在需要时调用您的方法。

最后你总是赢:)


s
shA.t

编程口语

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

例如,假设我们有两个类:Dog 和 Cat。两者都有相同的品质/状态:年龄、大小、体重。因此,我可以创建一个名为 AnimalService 的服务类,而不是创建一个名为 DogService 和 CatService 的服务类,它仅允许在 Dog 和 Cat 使用接口 IAnimal 时使用它们。

但是,从务实的角度来说,它有一些倒退。

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

b) 它有一些限制。例如,我们来谈谈DogCat的情况,我想为狗添加一个新的服务(功能)。假设我要计算训练一只狗需要多少天(trainDays()),对于猫来说它没用,不能训练猫(我在开玩笑)。

b.1) 如果我将 trainDays() 添加到服务 AnimalService 那么它也适用于猫,它根本无效。

b.2) 我可以在 trainDays() 中添加一个条件,用于评估使用哪个类。但它会彻底破坏 IoC。

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


关于臃肿的类/接口:您不必总是重用每个接口。有时,将一个大接口拆分为多个较小的接口以查看其功能边界是有意义的。较小的接口也更容易在其他实现中重用。它还鼓励您在任何有意义的地方对接口进行编码。考虑“接口隔离”。仅仅因为你使用了一个接口并不意味着你是解耦的。单个胖接口是没用的。 - 只是我的 2 美分 :)
S
Sergiu Starciuc

控制反转是将控制从库转移到客户端。当我们谈论将函数值(lambda 表达式)注入(传递)到控制(更改)库函数行为的高阶函数(库函数)中时,它更有意义。

因此,这种模式的一个简单实现(具有巨大影响)是一个高阶库函数(它接受另一个函数作为参数)。库函数通过让客户端能够提供“控制”函数作为参数来转移对其行为的控制。

例如,“map”、“flatMap”等库函数是 IoC 实现。

当然,受限的 IoC 版本是例如布尔函数参数。客户端可以通过切换布尔参数来控制库函数。

将库依赖项(携带行为)注入库的客户端或框架也可以视为 IoC


V
V. S.

由于该问题已经有很多答案,但没有一个显示反转控制术语的细分,我看到有机会给出更简洁和有用的答案。

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

控制反转分为三种类型:

接口反转提供者不应定义接口。相反,消费者应该定义接口,而提供者必须实现它。接口反转允许消除每次添加新提供者时修改消费者的必要性。

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

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


c
cattarantadoughan

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

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

没有 IoC:

*家长:只有当我问你问题时,你才能说话,只有在我允许的情况下,你才能行动。

家长:也就是说,如果我不问你,你就不能问我能不能吃饭、玩耍、上厕所甚至睡觉。

家长:你想吃吗?

孩子:没有。

家长:好的,我会回来的。等等我。

孩子:(想玩,但家长没问,孩子什么都做不了)。

1小时后...

家长:我回来了。你想玩吗?

孩子:是的。

家长:已获得许可。

孩子:(终于可以玩了)。

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

使用国际奥委会:

孩子现在可以提出问题,而家长可以回答并获得许可。简单来说就是控制反转!孩子现在可以随时自由提问,尽管在权限方面仍然依赖于父母,但他不依赖于说话/提问的方式。

从技术上讲,这与控制台/shell/cmd 与 GUI 交互非常相似。 (这是 Mark Harrison 的答案高于第二个最佳答案)。在控制台中,您依赖于向您询问/显示的内容,如果不先回答问题,您将无法跳转到其他菜单和功能;遵循严格的顺序流程。 (以编程方式,这就像一个方法/函数循环)。然而,使用 GUI,菜单和功能被布局,用户可以选择它需要的任何东西,从而拥有更多的控制和更少的限制。 (以编程方式,菜单在选择并发生操作时具有回调)。


s
shA.t

在类中创建对象称为紧耦合,Spring 通过遵循设计模式(DI/IOC)来消除这种依赖关系。在构造函数中传递的类对象中,而不是在类中创建。此外,我们在构造函数中给出了超类引用变量来定义更通用的结构。


R
Rush Frisby

使用 IoC,您不会更新您的对象。您的 IoC 容器将执行此操作并管理它们的生命周期。

它解决了必须手动将一种对象的每个实例化更改为另一种的问题。

当您的功能可能会在未来发生变化或可能会因所使用的环境或配置而有所不同时,这是合适的。