提问人:Mike Minutillo 提问时间:8/6/2008 最后编辑:Mehdi CharifeMike Minutillo 更新时间:8/13/2023 访问量:695641
什么是控制反转?
What is Inversion of Control?
答:
维基百科文章。对我而言,控制权反转就是将按顺序编写的代码转换为委托结构。你的程序不是显式控制一切,而是设置一个类或库,其中包含某些函数,以便在某些事情发生时被调用。
它解决了代码重复问题。例如,在过去,您可以手动编写自己的事件循环,轮询系统库以查找新事件。如今,大多数现代 API 您只需告诉系统库您感兴趣的事件,它就会在它们发生时通知您。
控制反转是减少代码重复的实用方法,如果您发现自己复制了整个方法并且只更改了一小部分代码,则可以考虑使用控制反转来解决它。在许多语言中,通过委托、接口甚至原始函数指针的概念,控制反转变得容易。
并非在所有情况下都使用,因为以这种方式编写程序的流程可能更难遵循。在编写将要重用的库时,这是一种设计方法的有用方法,但除非它真正解决了代码重复问题,否则应该在您自己的程序的核心中谨慎使用。
评论
我同意 NilObject,但我想补充一点:
如果您发现自己复制了整个方法并且只更改了一小部分代码,则可以考虑使用控制反转来解决它
如果你发现自己在复制和粘贴代码,你几乎总是做错了什么。编纂为设计原则一次和只有一次。
控制反转是你在程序回调时得到的,例如,像一个 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
所以现在控制权是颠倒的......计算机不是以固定的顺序接受用户输入,而是用户控制数据的输入顺序以及数据在数据库中的保存时间。
基本上,任何带有事件循环、回调或执行触发器的东西都属于这一类。
评论
控制反转
(IoC) 模式是关于提供任何类型的 ,它“实现”和/或控制反应,而不是我们自己直接行动(换句话说,反转和/或将控制重定向到外部处理程序/控制器)。
依赖关系注入
(DI) 模式是 IoC 模式的更具体版本,旨在从代码中删除依赖关系。callback
每个实现都可以考虑,但不应该称之为 ,因为实现依赖注入比回调更难(不要通过使用通用术语“IoC”来降低产品的价值)。
DI
IoC
IoC
以 DI 为例,假设您的应用程序有一个文本编辑器组件,并且您希望提供拼写检查。您的标准代码如下所示:
public class TextEditor {
private SpellChecker checker;
public TextEditor() {
this.checker = new SpellChecker();
}
}
我们在这里所做的是在 和 .
在 IoC 场景中,我们会执行如下操作:TextEditor
SpellChecker
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
请注意,就像 IoC 是许多其他模式的基础一样,上面的示例只是众多依赖注入类型中的一种,例如:
- 构造函数注入。
其中的实例将自动或类似于上述手动传递给构造函数。
IocSpellChecker
- 二传手注入。
其中的实例将通过 setter 方法或属性传递。
IocSpellChecker
public
- 服务查找和/或服务定位器
哪里会向已知的提供者询问类型的全局使用的实例(服务)(并且可能没有存储所述实例,而是一次又一次地询问提供者)。
TextEditor
IocSpellChecker
评论
但我认为你必须非常小心。如果你过度使用这种模式,你将做出非常复杂的设计,甚至更复杂的代码。
就像TextEditor的这个例子一样:如果你只有一个SpellChecker,也许真的没有必要使用IoC?除非你需要编写单元测试什么的......
无论如何:要合理。设计模式是好的做法,但不是圣经可以传讲的。不要到处贴它。
评论
控制反转是一种用于解耦系统中组件和层的模式。该模式是通过在构造组件时将依赖项注入组件来实现的。这些依赖关系通常作为接口提供,用于进一步解耦并支持可测试性。IoC / DI 容器,例如 Castle Windsor、Unity 是可用于提供 IoC 的工具(库)。这些工具提供了超越简单依赖管理的扩展功能,包括生命周期、AOP/拦截、策略等。
一个。减轻组件负责管理其依赖项的负担。
湾。提供在不同环境中交换依赖项实现的功能。
三.允许通过模拟依赖项来测试组件。
d.提供在整个应用程序中共享资源的机制。一个。在进行测试驱动开发时至关重要。如果没有 IoC,测试可能很困难,因为被测组件与系统的其余部分高度耦合。
湾。在开发模块化系统时至关重要。模块化系统是指无需重新编译即可更换其组件的系统。
三.如果有许多横切问题需要解决,尤其是在企业应用程序中,这一点至关重要。
评论
对我来说,IoC / DI 正在将依赖项推送到调用对象。超级简单。
非技术性的答案是能够在打开汽车之前更换汽车中的发动机。如果一切正常(界面),你就很好。
使用像温莎城堡这样的集装箱,可以更好地解决维护问题。能够在不更改任何代码的情况下将进入数据库的组件换成使用基于文件的持久性的组件,这真是太棒了(配置更改,您就完成了)。
一旦你进入仿制药,它就会变得更好。想象一下,有一个消息发布者接收记录并发布消息。它不在乎它发布什么,但它需要一个映射器来将某些内容从记录中获取到消息中。
public class MessagePublisher<RECORD,MESSAGE>
{
public MessagePublisher(IMapper<RECORD,MESSAGE> mapper,IRemoteEndpoint endPointToSendTo)
{
//setup
}
}
我曾经写过一次,但现在如果我发布不同类型的消息,我可以将许多类型注入到这组代码中。我还可以编写映射器,获取相同类型的记录并将它们映射到不同的消息。将 DI 与泛型结合使用使我能够编写很少的代码来完成许多任务。
哦,是的,存在可测试性问题,但它们是次要的,而不是 IoC/DI 的好处。
我非常喜欢 IoC/DI。
3 .当你有一个稍微复杂的中型项目时,它就变得更加合适。我想说的是,当你开始感到疼痛的那一刻,它就变得合适了。
评论
例如,task#1 是创建对象。 没有 IOC 概念,任务 #1 应该由程序员完成,但有了 IOC 概念,任务 #1 将由容器完成。
简而言之,控制从程序员到容器颠倒过来。因此,它被称为控制反转。
我在这里找到了一个很好的例子。
评论
在使用控制反转之前,您应该充分了解它有其优点和缺点的事实,并且如果您这样做,您应该知道为什么要使用它。
优点:
- 您的代码是解耦的,因此您可以轻松地将接口的实现与替代实现交换
- 它是针对接口而不是实现进行编码的强大动力
- 为代码编写单元测试非常容易,因为它只依赖于它在构造函数/setter 中接受的对象,并且您可以轻松地使用正确的对象单独初始化它们。
缺点:
- IoC 不仅反转了程序中的控制流,还大大遮蔽了程序。这意味着您不能再只是读取代码并从一个地方跳到另一个地方,因为通常在代码中的连接不再在代码中。相反,它位于 XML 配置文件或批注中,以及解释这些元数据的 IoC 容器的代码中。
- 出现了一类新的错误,其中您的 XML 配置或注释错误,您可能会花费大量时间找出为什么 IoC 容器在某些条件下将 null 引用注入到您的某个对象中。
就我个人而言,我看到了 IoC 的优点,我真的很喜欢它们,但我倾向于尽可能避免 IoC,因为它将您的软件变成了一个类的集合,这些类不再构成“真正的”程序,而只是需要通过 XML 配置或注释元数据组合在一起的东西,如果没有它,它们就会崩溃(并分崩离析)。
评论
什么是控制反转?
如果按照以下简单的两个步骤操作,则已完成控制反转:
- 将“做什么”部分与“何时”部分分开。
- 确保当零件尽可能少地知道哪个零件;反之亦然。
根据您用于实现的技术/语言,每个步骤都有几种可能的技术。
--
控制反转 (IoC) 的反转部分是令人困惑的事情;因为反转是相对项。理解 IoC 的最好方法是忘记这个词!
--
例子
- 事件处理。事件处理程序(做什么部分) -- 引发事件(何时执行部分)
- 依赖注入。构造依赖关系(做什么部分)的代码 -- 在需要时为客户端实例化和注入该依赖关系,这通常由 DAGGER(何时执行部分)等 DI 工具处理。
- 接口。组件客户端(何时执行部分) -- 组件接口实现(执行什么操作部分)
- xUnit 夹具。Setup 和 TearDown(做什么部分)——xUnit 框架在开始时调用 Setup,在结束时调用 TearDown(何时执行部分)
- 模板方法设计模式。模板方法 When-to-do 部分 -- 原始子类实现 what-to-do 部分
- COM 中的 DLL 容器方法。 DllMain、DllCanUnload 等(做什么部分) -- COM/OS(何时做部分)
评论
似乎关于“IoC”这个首字母缩略词和它所代表的名字最令人困惑的是它的名字太迷人了——几乎是一个噪音的名字。
我们真的需要一个名称来描述过程驱动编程和事件驱动编程之间的区别吗?好吧,如果我们需要的话,但是我们是否需要选择一个全新的“比生活更重要”的名字,它比它解决的更令人困惑?
评论
假设我们在一家酒店开会。
我们邀请了很多人,所以我们遗漏了很多水壶和许多塑料杯。
当有人想喝水时,他/她会装满杯子,喝水,然后把杯子扔在地板上。
大约一个小时后,我们的地板上铺满了塑料杯和水。
让我们在反转控件后尝试一下:
想象一下,在同一个地方开会,但现在的服务员只有一个玻璃杯(辛格尔顿),而不是塑料杯
当有人想喝酒时,服务员会为他们准备一杯。他们喝了酒,然后还给了服务员。
撇开卫生问题不谈,使用服务员(过程控制)更加有效和经济。
而这正是 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
评论
可以在这里找到一个非常简单的书面解释
http://binstock.blogspot.in/2008/01/excellent-explanation-of-dependency.html
它说——
“任何重要的应用程序都由两个或多个类组成,这些类 相互协作以执行一些业务逻辑。 传统上,每个对象都负责获取自己的 对与之协作的对象(其依赖项)的引用。 应用 DI 时,对象在创建时会被赋予其依赖关系 时间由某个外部实体来协调 系统。换句话说,依赖关系被注入到对象中。
控制反转(或IoC)是关于获得自由(你结婚了,你失去了自由,你被控制了。你离婚了,你刚刚实施了控制反转。这就是我们所说的“解耦”。良好的计算机系统不鼓励一些非常密切的关系。更灵活(您办公室的厨房只提供干净的自来水,这是您想喝水时的唯一选择。您的老板通过设置一台新的咖啡机来实施控制反转。现在,您可以灵活地选择自来水或咖啡。减少依赖(你的伴侣有工作,你没有工作,你在经济上依赖你的伴侣,所以你被控制了。你找到了一份工作,你已经实施了控制反转。良好的计算机系统鼓励独立性。
当你使用台式电脑时,你已经被奴役(或者说,被控制)。你必须坐在屏幕前看它。使用键盘键入并使用鼠标导航。一个写得不好的软件会更多地奴役你。如果你用笔记本电脑代替你的台式机,那么你的控制就有点颠倒了。您可以轻松地携带它并四处走动。因此,现在您可以使用计算机控制您的位置,而不是您的计算机控制它。
通过实现控制反转,软件/对象使用者可以获得对软件/对象的更多控制/选项,而不是被控制或拥有更少的选项。
考虑到上述想法。我们仍然错过了 IoC 的一个关键部分。在 IoC 场景中,软件/对象使用者是一个复杂的框架。这意味着您创建的代码不是由您自己调用的。现在让我们解释一下为什么这种方式更适合 Web 应用程序。
假设您的代码是一组工作线程。他们需要制造一辆汽车。这些工人需要一个地方和工具(一个软件框架)来制造汽车。传统的软件框架就像一个拥有许多工具的车库。因此,工人们需要自己制定计划,并使用工具来制造汽车。制造汽车不是一件容易的事,工人们很难正确地计划和合作。现代软件框架将像一个现代化的汽车工厂,拥有所有的设施和管理人员。工人不必制定任何计划,经理(框架的一部分,他们是最聪明的人,制定了最复杂的计划)将帮助协调,以便工人知道何时完成他们的工作(框架调用你的代码)。员工只需要足够灵活,就可以使用经理给他们的任何工具(通过使用依赖注入)。
尽管工作人员将顶层管理项目的控制权交给了经理(框架)。但有一些专业人士帮忙是件好事。这才是IoC的真正概念来源。
具有 MVC 体系结构的现代 Web 应用程序依赖于框架来执行 URL 路由,并将控制器放在适当的位置以供框架调用。
依赖注入和控制反转是相关的。依赖注入在微观层面,控制反转在宏观层面。你必须吃完每一口(实现 DI)才能吃完一顿饭(实现 IoC)。
评论
假设你是一个对象。然后你去一家餐馆:
没有 IoC:你要的是“苹果”,当你问得更多时,你总是会得到苹果。
使用 IoC:您可以要求“水果”。每次上菜时,您都可以获得不同的水果。例如,苹果、橙子或西瓜。
因此,显然,当您喜欢这些品种时,IoC 是首选。
控制倒置是关于分离关注点。
没有 IoC:你有一台笔记本电脑,你不小心打破了屏幕。的,你发现相同型号的笔记本电脑屏幕在市场上无处可去。所以你被困住了。
使用 IoC:您有一台台式计算机,但不小心打破了屏幕。您会发现您几乎可以从市场上购买任何桌面显示器,并且它与您的桌面配合得很好。
在这种情况下,您的桌面成功实现了 IoC。它接受各种类型的显示器,而笔记本电脑则不接受,它需要一个特定的屏幕来修复。
评论
IoC 是关于反转代码和第三方代码(库/框架)之间的关系:
- 在正常的软件开发中,您编写 main() 方法并调用“库”方法。一切尽在掌握 :)
- 在 IoC 中,“框架”控制 main() 并调用您的方法。该框架处于控制之中:(
DI(依赖注入)是关于控制如何在应用程序中流动。传统的桌面应用程序具有从应用程序(main()方法)到其他库方法调用的控制流,但是在DI控制流中,框架负责启动应用程序,初始化它并在需要时调用方法。
最后,你总是赢:)
我将写下我对这两个术语的简单理解:
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 功能。
快速示例:控制反转是关于获得自由、更大的灵活性和更少的依赖性。当你使用台式电脑时,你是被奴役的(或者说,被控制的)。你必须坐在屏幕前看它。使用键盘打字,使用鼠标导航。一个糟糕的软件会更多地奴役你。如果你用笔记本电脑取代了你的台式机,那么你就有点颠倒了控制。您可以轻松地携带它并四处走动。因此,现在您可以使用计算机控制您的位置,而不是计算机控制它
。
通过实现控制反转,软件/对象消费者可以获得对软件/对象的更多控制/选项,而不是被控制或拥有更少的选项。
将控制反转作为设计准则具有以下目的:
某个任务的执行与实施是脱钩的。
每个模块都可以专注于它的设计目的。
模块不对其他系统做什么做任何假设,而是依赖于它们的契约。
更换模块对其他模块
没有副作用 我将在这里保持抽象,您可以访问以下链接以详细了解该主题。
一本好书,有例子
编程口语
简单来说,IoC:它是使用接口作为特定事物(例如字段或参数)的一种方式,作为某些类可以使用的通配符。它允许代码的可重用性。
例如,假设我们有两个类:狗和猫。两者都具有相同的品质/状态:年龄、大小、体重。因此,与其创建一个名为 DogService 和 CatService 的服务类,不如创建一个名为 AnimalService 的服务类,该类仅允许在 Dog 和 Cat 使用接口 IAnimal 时才使用它们。
但是,从务实上讲,它有一些倒退。
a) 大多数开发人员不知道如何使用它。例如,我可以创建一个名为 Customer 的类,并且可以自动(使用 IDE 的工具)创建一个名为 ICustomer 的接口。因此,找到一个充满类和接口的文件夹并不罕见,无论这些接口是否会被重用。它被称为臃肿。有些人可能会争辩说“也许将来我们可以使用它”。:-|
b) 它有一些局限性。例如,让我们谈谈狗和猫的情况,我想添加一个只为狗的新服务(功能)。假设我想计算我需要训练狗的天数(trainDays()
),对于猫来说没用,猫不能训练(我在开玩笑)。
b.1) 如果我添加到服务性动物服务中,那么它也适用于猫,而且它根本不有效。trainDays()
b.2) 我可以添加一个条件,在其中评估使用哪个类。但它将完全破坏 IoC。trainDays()
b.3) 我可以为新功能创建一个名为 DogService 的新服务类。但是,这将增加代码的可维护性,因为我们将为 Dog 提供两类服务(具有类似的功能),这很糟糕。
评论
在类中创建对象称为紧耦合,Spring 通过遵循设计模式 (DI/IOC) 来消除这种依赖关系。在哪个类对象中传递构造函数而不是在类中创建。此外,我们在构造函数中给出了超类引用变量来定义更通用的结构。
使用 IoC,您不会对对象进行新手。您的 IoC 容器将执行此操作并管理它们的生存期。
它解决了必须手动将一种类型的对象的每个实例化更改为另一种类型的问题。
当您的功能将来可能会更改,或者可能因所使用的环境或配置而异时,这是合适的。
控制反转是一个通用原则,而依赖注入将这一原则实现为对象图构造的设计模式(即配置控制对象如何相互引用,而不是对象本身控制如何获取对另一个对象的引用)。
将控制反转视为一种设计模式,我们需要看看我们正在反转的内容。依赖注入反转了对构建对象图的控制。如果用通俗的话来说,控制反转意味着程序中控制流的变化。例如。在传统的独立应用程序中,我们有 main 方法,从那里将控件传递给其他第三方库(以防万一,我们使用了第三方库的功能),但通过控制反转,控制权从第三方库代码转移到我们的代码,因为我们正在接受第三方库的服务。但是在程序中还有其他方面需要反转 - 例如调用方法和线程来执行代码。
对于那些对控制反转感兴趣的人,已经发表了一篇论文,概述了控制反转作为一种设计模式的更完整的图景(OfficeFloor:使用办公模式改进软件设计 http://doi.acm.org/10.1145/2739011.2739013,可从 http://www.officefloor.net/about.html 免费下载副本)。
确定的是以下关系:
控制反转(针对方法)= 依赖项(状态)注入 + 延续注入 + 线程注入
上述控制反转关系摘要 - http://dzone.com/articles/inversion-of-coupling-control
评论
只回答第一部分。 这是什么?
控制反转 (IoC) 意味着首先创建类的依赖项实例和后一个类实例(可以选择通过构造函数注入它们),而不是先创建类的实例,然后类实例创建依赖项的实例。 因此,控制反转会反转程序的控制流。调用方不是控制控制流(在创建依赖项时),而是控制程序的控制流。
评论
为了理解这个概念,控制反转 (IoC) 或依赖反转原则 (DIP) 涉及两个活动:抽象和反转。 依赖注入 (DI) 只是为数不多的反转方法之一。
要阅读更多关于这方面的信息,你可以在这里阅读我的博客
- 这是什么?
这是一种让实际行为来自边界之外的做法(面向对象编程中的类)。边界实体只知道它的抽象(例如接口、抽象类、面向对象编程中的委托)。
- 它能解决什么问题?
在编程方面,IoC 试图通过模块化代码、解耦其各个部分并使其可单元测试来解决单体代码问题。
- 什么时候合适,什么时候不合适?
大多数时候,它是合适的,除非你只想要单体代码(例如非常简单的程序)
我喜欢这个解释: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
- 更灵活,可以在运行时决定实现
- 代码单元隔离,测试更简单
...
评论
我知道这里已经给出了答案。但我仍然认为,这里必须详细讨论一些关于控制反转的基础知识,供未来的读者使用。
控制反转 (IoC) 建立在一个非常简单的原则之上,称为好莱坞原则。它说,
不要打电话给我们,我们会给你打电话
意思是,不要去好莱坞实现你的梦想,而是如果你值得,那么好莱坞就会找到你,让你的梦想成真。差不多是倒置了,对吧?
现在,当我们讨论IoC的原理时,我们常常忘记好莱坞。对于IoC来说,必须有三个元素,一个好莱坞,一个你和一个任务,比如实现你的梦想。
在我们的编程世界中,好莱坞代表一个通用框架(可能由你或其他人编写),你代表你编写的用户代码,任务代表你想用你的代码完成的事情。现在你再也不用自己去触发你的任务了,而不是在 IoC 中!相反,你已经设计了一切,以便你的框架将为你触发你的任务。因此,您构建了一个可重用的框架,可以使某人成为英雄或另一个人成为恶棍。但这个框架总是负责的,它知道什么时候选择某人,而这个人只知道它想成为什么。
这里将给出一个现实生活中的例子。假设您要开发一个 Web 应用程序。因此,您创建了一个框架,该框架将处理 Web 应用程序应处理的所有常见事情,例如处理 http 请求、创建应用程序菜单、提供页面、管理 cookie、触发事件等。
然后你在框架中留下一些钩子,你可以在其中放置更多的代码来生成自定义菜单、页面、cookie 或记录一些用户事件等。在每个浏览器请求中,您的框架将运行并执行您的自定义代码(如果挂钩),然后将其返回给浏览器。
所以,这个想法非常简单。与其创建一个控制一切的用户应用程序,不如首先创建一个可重用的框架来控制一切,然后编写自定义代码并将其挂接到框架上,以便及时执行这些代码。
Laravel 和 EJB 就是这种框架的例子。
参考:
https://martinfowler.com/bliki/InversionOfControl.html
https://en.wikipedia.org/wiki/Inversion_of_control
评论
我在这里找到了一个非常明显的例子,它解释了“控件是如何反转的”。
经典代码(无依赖注入)
以下是不使用 DI 的代码的大致工作方式:
- 应用程序需要 Foo(例如控制器),因此:
- 应用程序创建 Foo
- 应用程序调用 Foo
- Foo 需要 Bar(例如服务),因此:
- Foo 创建酒吧
- Foo calls 酒吧
- Bar 需要 Bim(服务、存储库等),因此:
- Bar 创建 Bim
- 酒吧做点什么
使用依赖注入
以下是使用 DI 的代码的大致工作方式:
- 应用程序需要 Foo,这需要 Bar,需要 Bim,所以:
- 应用程序创建 BIM
- 应用程序创建 Bar 并为其提供 Bim
- 应用程序创建 Foo 并为其提供 Bar
- 应用程序调用 Foo
- Foo calls 酒吧
- 酒吧做点什么
- Foo calls 酒吧
依赖项的控制从被调用到被调用。
它能解决什么问题?
依赖注入可以很容易地与注入类的不同实现进行交换。在单元测试时,您可以注入一个虚拟实现,这使得测试变得容易得多。
例如:假设您的应用程序将用户上传的文件存储在 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 实现时,您必须替换构建 GoogleDriveService 对象的所有行并使用 DropboxService。此外,在测试 SomeController 类时,构造函数始终需要 GoogleDriveService 类,并且会触发此类的实际方法。new
什么时候合适,什么时候不合适?在我看来,当你认为有一个类有(或可以有)替代实现时,你就会使用 DI。
评论
控制反转是指当你去杂货店,你的妻子给你要购买的产品清单时。
在编程术语中,她将一个回调函数传递给您正在执行的函数 - 。getProductList()
doShopping()
它允许函数的用户定义它的某些部分,使其更加灵活。
评论
getProductList()
控制反转是指将控制权从库转移到客户端。当我们谈论将函数值(lambda 表达式)注入(传递)到控制(更改)库函数行为的高阶函数(库函数)的客户端时,它更有意义。
因此,此模式的简单实现(具有巨大的影响)是高阶库函数(它接受另一个函数作为参数)。库函数通过使客户端能够提供“控制”函数作为参数来转移对其行为的控制。
例如,“map”、“flatMap”等库函数是 IoC 实现。
当然,有限的 IoC 版本是布尔函数参数。客户端可以通过切换布尔参数来控制库函数。
将库依赖项(携带行为)注入库的客户端或框架也可以被视为 IoC
控制反转意味着您可以控制组件(类)的行为方式。为什么它被称为“反转”,因为在这种模式之前,类是硬连线的,并且对它们将做什么是明确的,例如
导入具有 AND 类的库。现在自然而然地,这只会检查英语的拼写。假设如果你想处理德语并能够进行拼写检查,你可以控制它。TextEditor
SpellChecker
SpellChecker
TextEditor
使用 IoC,这种控制是反转的,即它给你,如何?该库将实现如下内容:
它将有一个类,然后它将有一个(这是一个接口而不是一个具体的类),当您在 IoC 容器(例如 Spring)中配置内容时,您可以提供自己的“ISpellChecker”实现,它将检查德语的拼写。因此,拼写检查如何工作的控制权是从该库中获取并交给您的。这就是 IoC。TextEditor
ISpeallChecker
SpellChecker
这是什么?反转(耦合)控制,更改方法签名的耦合方向。使用反转控制时,方法签名的定义由方法实现(而不是方法的调用方)决定。完整解释在这里
它解决了什么问题?方法的自上而下耦合。这样就不需要重构了。
什么时候适合使用,什么时候不适合使用?对于定义明确且不会进行太多更改的小型应用程序,这可能是开销。但是,对于将要发展的不太明确的应用程序,它减少了方法签名的固有耦合。这为开发人员提供了更大的自由来发展应用程序,避免了对代码进行昂贵的重构的需要。基本上,允许应用程序在几乎不需要返工的情况下发展。
要理解 IoC,我们应该谈谈依赖反转。
依赖反转:依赖于抽象,而不是具体。
控制反转:主与抽象,以及主如何成为系统的粘合剂。
我用一些很好的例子写了这个,你可以在这里查看它们:
https://coderstower.com/2019/03/26/dependency-inversion-why-you-should't-avoid-it/
https://coderstower.com/2019/04/02/main-and-abstraction-the-decoupled-peers/
https://coderstower.com/2019/04/09/inversion-of-control-putting-all-together/
真的不明白为什么有很多错误的答案,甚至被接受的答案也不是很准确,使事情变得难以理解。真相总是简单明了的。
正如@Schneider在哈里森@Mark回答中评论的那样,请阅读马丁·福勒(Martin Fowler)讨论IoC的帖子。
https://martinfowler.com/bliki/InversionOfControl.html
我最喜欢的一个是:
这种现象就是控制反转(又称好莱坞原则——“不要打电话给我们,我们就给你打电话”)。
为什么?
Wiki for IoC,我可能会引用一个片段。
控制反转用于增加程序的模块化并使其可扩展......然后在 2004 年由 Robert C. Martin 和 Martin Fowler 进一步推广。
罗伯特·马丁(Robert C. Martin):《.<<Clean Code: A Handbook of Agile Software Craftsmanship>>
马丁·福勒(Martin Fowler):《.<<Refactoring: Improving the Design of Existing Code>>
我已经阅读了很多答案,但如果有人仍然感到困惑并且需要一个加号的超“外行术语”来解释 IoC,以下是我的看法:
想象一下父母和孩子互相交谈。
不使用 IoC:
*家长:只有当我问你问题时,你才能说话,只有当我允许你时,你才能行动。
家长:这意味着,如果我不问你,你就不能问我你能不能吃饭、玩耍、上厕所,甚至不能睡觉。
家长:你想吃饭吗?
孩子:没有。
家长:好的,我会回来的。等等我。
孩子:(想玩,但由于父母没有问题,孩子什么都做不了)。
1小时后...
家长:我回来了。你想玩吗?
孩子:是的。
父级:授予的权限。
孩子:(终于可以玩了)。
这个简单的方案说明了控件以父级为中心。孩子的自由受到限制,很大程度上取决于父母的问题。孩子只有在被要求说话时才能说话,并且只有在获得许可时才能行动。
使用 IoC:
孩子现在有能力提出问题,家长可以回答和权限。简单地说,就是控制是反转的! 孩子现在可以随时自由提问,尽管在权限方面仍与父母存在依赖关系,但他在说话/提问的方式上并不依赖。
从技术上解释,这与控制台/shell/cmd 与 GUI 交互非常相似。(这是马克·哈里森(Mark Harrison)的答案,上面是第2个顶级答案)。 在控制台中,您依赖于被询问/显示的内容,如果不先回答它的问题,您就无法跳转到其他菜单和功能;遵循严格的顺序流程。(以编程方式,这就像一个方法/函数循环)。 但是,使用 GUI,菜单和功能是布局的,用户可以选择所需的任何内容,从而拥有更多的控制权并减少限制。(以编程方式,菜单在选中并执行操作时具有回调)。
由于这个问题已经有很多答案,但没有一个显示控制反转术语的一些细分,我认为有机会给出一个更简洁和有用的答案。
控制反转是一种实现依赖关系反转原则 (DIP) 的模式。DIP 声明如下: 1.高级模块不应依赖于低级模块。两者都应该依赖于抽象(例如接口)。2. 抽象不应该依赖于细节。细节(具体实现)应该取决于抽象。
控制反转有三种类型:
接口反转提供程序不应定义接口。相反,使用者应该定义接口,提供者必须实现它。接口反转允许在每次添加新提供程序时都无需修改使用者。
流量反转流反转会更改程序的流控制。例如,您有一个控制台应用程序,要求您输入许多参数,并且在输入每个参数后,您都必须按 Enter。您可以在此处应用 Flow Inversion 并实现一个桌面应用程序,用户可以在其中选择要输入的参数序列,用户可以编辑参数,在最后一步,用户只需按一次 Enter。
创建反转它可以通过以下模式实现:工厂模式、服务定位器和依赖注入。创建反转有助于消除类型之间的依赖关系,将必要对象的创建过程移到使用这些依赖对象的程序类型之外。为什么依赖关系不好?下面是几个示例:在代码中直接创建新对象会使测试更加困难;如果不重新编译,就不可能更改程序集中的引用(这会导致违反 OCP 原则);您无法轻松地将桌面 UI 替换为 Web UI。
控制权倒置是计划中责任转移的指标。
每当依赖项被授予直接作用于调用方空间的能力时,都会发生控制反转。
最小的 IoC 是通过引用传递变量,让我们先看一下非 IoC 代码:
function isVarHello($var) {
return ($var === "Hello");
}
// Responsibility is within the caller
$word = "Hello";
if (isVarHello($word)) {
$word = "World";
}
现在,让我们通过将结果的责任从调用方转移到依赖项来反转控制:
function changeHelloToWorld(&$var) {
// Responsibility has been shifted to the dependency
if ($var === "Hello") {
$var = "World";
}
}
$word = "Hello";
changeHelloToWorld($word);
下面是另一个使用 OOP 的示例:
<?php
class Human {
private $hp = 0.5;
function consume(Eatable $chunk) {
// $this->chew($chunk);
$chunk->unfoldEffectOn($this);
}
function incrementHealth() {
$this->hp++;
}
function isHealthy() {}
function getHungry() {}
// ...
}
interface Eatable {
public function unfoldEffectOn($body);
}
class Medicine implements Eatable {
function unfoldEffectOn($human) {
// The dependency is now in charge of the human.
$human->incrementHealth();
$this->depleted = true;
}
}
$human = new Human();
$medicine = new Medicine();
if (!$human->isHealthy()) {
$human->consume($medicine);
}
var_dump($medicine);
var_dump($human);
*) 免责声明:现实世界的人类使用消息队列。
评论
我在使用库或框架的上下文中考虑控制反转。
传统的“控制”方式是,我们构建一个控制器类(通常是主类,但它可以是任何东西),导入一个库,然后使用控制器类来“控制”软件组件的动作。就像你的第一个 C/Python 程序一样(在 Hello World 之后)。
import pandas as pd
df = new DataFrame()
# Now do things with the dataframe.
在这种情况下,我们需要知道 Dataframe 是什么才能使用它。您需要知道要使用什么方法,如何取值等等。如果通过多态性将其添加到自己的类中,或者只是重新调用它,则类将需要 DataFrame 库才能正常工作。
“控制反转”意味着过程被颠倒。不是类控制库、框架或引擎的元素,而是注册类并将它们发送回引擎进行控制。换句话说,IoC 可能意味着我们正在使用我们的代码来配置框架。您也可以将其视为类似于我们在列表中使用函数或处理列表中数据的方式,只是将其应用于整个应用程序。map
filter
如果你是构建引擎的人,那么你可能正在使用依赖注入方法(如上所述)来实现这一点。如果你是使用引擎的人(更常见),那么你应该能够只声明类,添加适当的符号,让框架为你完成剩下的工作(例如,创建路由、分配 servlet、设置事件、输出小部件等)。
用这么多先前的答案来回答这个问题,我觉得有点尴尬,但我只是认为任何答案都不足以简单地陈述这个概念。
所以,我们开始吧......
在非 IOC 应用程序中,您将对流程进行编码,并在其中包含所有详细步骤。考虑一个创建报表的程序 - 它将包括用于设置打印机连接、打印页眉、循环访问详细记录、打印页脚、执行页面进纸等的代码。
在报表程序的 IOC 版本中,您将配置通用的、可重用的报表类的实例,即包含用于打印报表的流程但其中不包含任何详细信息的类。您提供的配置可以使用 DI 来指定报表应调用哪个类来打印页眉,报表应调用哪个类来打印详细信息行,以及报表应调用哪个类来打印页脚。
因此,控制的反转来自控制过程不是您的代码,而是包含在一个外部的、可重用的类(Report)中,该类允许您指定或注入(通过 DI)报告的详细信息 - 页眉、详细信息行、页脚。
您可以使用相同的 Report 类(控制类)生成任意数量的不同报表 - 通过提供不同的详细信息类集。您依赖于 Report 类来提供控件,并且仅通过注入指定报表之间的差异,从而反转了控件。
在某些方面,IOC 可以与驱动器备份应用程序进行比较 - 备份始终执行相同的步骤,但备份的文件集可能完全不同。
现在具体回答原来的问题......
- 这是什么?IOC 依赖于可重用的控制器类,并提供特定于您手头问题的详细信息。
- 它解决了什么问题?防止您不得不重述控制流程流。
- 什么时候适合使用,什么时候不适合使用?每当您创建控制流始终相同且仅更改细节的流程流时。在创建一次性自定义流程时,您不会使用它。
最后,IOC 不是 DI,DI 也不是 IOC——DI 通常可以在 IOC 中使用(为了说明抽象控件类的细节)。
无论如何 - 我希望这会有所帮助。
上一个:如何有效地配对一堆袜子?
下一个:方法和函数有什么区别?
评论