第一次遇到控制反转 (IoC) 时可能会非常混乱。
它是什么?它解决了哪个问题?什么时候适合使用,什么时候不适合?
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();
}
}
我们在这里所做的创建了 TextEditor
和 SpellChecker
之间的依赖关系。在 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
签名中。
控制反转是您在程序回调时得到的,例如 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)的反转部分是令人困惑的事情;因为倒置是相对的术语。理解 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(待办事项部分)
控制反转是关于分离关注点。
没有 IoC:你有一台笔记本电脑,但你不小心打破了屏幕。而且,您会发现市场上没有相同型号的笔记本电脑屏幕。所以你被困住了。
使用 IoC:您有一台台式计算机,但不小心打破了屏幕。你会发现你几乎可以从市场上买到任何桌面显示器,而且它与你的桌面配合得很好。
在这种情况下,您的桌面成功实现了 IoC。它接受各种类型的显示器,而笔记本电脑不接受,它需要一个特定的屏幕来固定。
控制反转(或 IoC)是关于获得自由(你结婚了,你失去了自由,你被控制了。你离婚了,你刚刚实施了控制反转。这就是我们所说的“解耦”。好的计算机系统不鼓励一些非常亲密的关系。)更多的灵活性(你办公室的厨房只供应干净的自来水,这是你想喝的唯一选择。你的老板通过设置一台新咖啡机实施了控制反转。现在你得到了选择自来水或咖啡的灵活性。)和更少的依赖(你的伴侣有工作,你没有工作,你在经济上依赖你的伴侣,所以你被控制了。你找到了工作,你已经实施了控制。好的计算机系统鼓励独立。)
当您使用台式计算机时,您已经从属(或者说,被控制)。你必须坐在屏幕前看着它。使用键盘键入并使用鼠标导航。一个写得不好的软件会更加奴役你。如果你用笔记本电脑代替你的台式机,那么你的控制就有点颠倒了。您可以轻松携带它并四处走动。因此,现在您可以使用计算机控制您所在的位置,而不是您的计算机控制它。
通过实施控制反转,软件/对象消费者可以获得对软件/对象的更多控制/选项,而不是被控制或拥有更少的选项。
带着以上的想法。我们仍然错过了 IoC 的一个关键部分。在 IoC 场景中,软件/对象消费者是一个复杂的框架。这意味着您创建的代码不是您自己调用的。现在让我们解释一下为什么这种方式更适合 Web 应用程序。
假设您的代码是一组工人。他们需要造一辆汽车。这些工人需要一个地方和工具(一个软件框架)来制造汽车。一个传统的软件框架就像一个有很多工具的车库。所以工人们需要自己制定计划,并使用工具来制造汽车。造车不是一件容易的事,工人们要好好规划和配合,真的很难。一个现代的软件框架就像一个现代化的汽车工厂,拥有所有的设施和管理人员。工人不必制定任何计划,经理(框架的一部分,他们是最聪明的人并制定了最复杂的计划)将帮助协调,以便工人知道何时完成他们的工作(框架调用您的代码)。工作人员只需要足够灵活以使用经理提供给他们的任何工具(通过使用依赖注入)。
尽管工作人员将顶层项目管理的控制权交给了管理者(框架)。但是最好有一些专业人士的帮助。这就是IoC的概念真正的来历。
具有 MVC 架构的现代 Web 应用程序依赖于框架来执行 URL 路由并将控制器放置在适当的位置以供框架调用。
依赖注入和控制反转是相关的。依赖注入是微观层面的,控制反转是宏观层面的。您必须吃完每一口食物(实施 DI)才能完成一顿饭(实施 IoC)。
在使用控制反转之前,您应该清楚地知道它有其优点和缺点,并且如果您这样做,您应该知道为什么要使用它。
优点:
您的代码被解耦,因此您可以轻松地将接口的实现与替代实现交换
它是针对接口而不是实现进行编码的强大动力
为您的代码编写单元测试非常容易,因为它只依赖于它在其构造函数/设置器中接受的对象,并且您可以轻松地用正确的对象单独初始化它们。
缺点:
IoC 不仅会反转程序中的控制流,还会使程序变得混乱。这意味着您不能再仅仅阅读您的代码并从一个地方跳到另一个地方,因为通常在您的代码中的连接不再在代码中。相反,它位于 XML 配置文件或注释以及解释这些元数据的 IoC 容器的代码中。
出现了一类新的错误,您的 XML 配置或注释错误,您可能会花费大量时间找出为什么您的 IoC 容器在某些条件下将空引用注入您的对象之一。
就我个人而言,我看到了 IoC 的优点,我真的很喜欢它们,但我倾向于尽可能避免使用 IoC,因为它会将您的软件变成一个类的集合,这些类不再构成“真正的”程序,而只是需要由XML 配置或注释元数据,没有它就会崩溃(并且崩溃)。
维基百科文章。对我来说,控制反转是把你顺序编写的代码变成一个委托结构。您的程序不是显式地控制一切,而是设置一个类或库,其中包含在某些事情发生时要调用的某些函数。它解决了代码重复。例如,在过去,您会手动编写自己的事件循环,轮询系统库以获取新事件。如今,大多数现代 API 您只需告诉系统库您感兴趣的事件,它就会让您知道它们何时发生。控制反转是减少代码重复的实用方法,如果您发现自己复制了整个方法而只更改了一小部分代码,您可以考虑使用控制反转来解决它。在许多语言中,通过委托、接口甚至原始函数指针的概念,控制反转很容易。它并不适合在所有情况下使用,因为以这种方式编写的程序流程可能更难遵循。在编写将被重用的库时,这是一种设计方法的有用方法,但除非它真正解决了代码重复问题,否则应该在您自己程序的核心中谨慎使用。
假设你是一个对象。你去一家餐馆:
没有 IoC:你要求“苹果”,当你要求更多时,你总是得到苹果。
使用 IoC:您可以要求“水果”。每次上菜时,您可以获得不同的水果。例如,苹果、橙子或西瓜。
因此,显然,当您喜欢这些品种时,IoC 是首选。
但我认为你必须非常小心。如果你过度使用这种模式,你会做出非常复杂的设计甚至更复杂的代码。
就像在这个 TextEditor 的例子中一样:如果你只有一个 SpellChecker,也许真的没有必要使用 IoC 吗?除非你需要写单元测试什么的……
总之:讲道理。设计模式是好的实践,但不是要传讲的圣经。不要到处粘。
对我来说,IoC / DI 将依赖关系推向调用对象。超级简单。
非技术性的答案是能够在您打开汽车之前更换汽车中的发动机。如果一切都连接正确(界面),那么你很好。
只回答第一部分。它是什么?
控制反转 (IoC) 意味着先创建依赖项实例,然后创建类的后一个实例(可选地通过构造函数注入它们),而不是先创建类实例,然后类实例创建依赖项实例。因此,控制反转会反转程序的控制流程。调用者控制程序的控制流,而不是被调用者控制控制流(同时创建依赖项)。
控制反转是一种用于解耦系统中的组件和层的模式。该模式是通过在构建组件时将依赖项注入组件来实现的。这些依赖关系通常作为接口提供,用于进一步解耦和支持可测试性。 IoC / DI 容器,例如 Castle Windsor、Unity 是可用于提供 IoC 的工具(库)。这些工具提供了超越简单依赖管理的扩展功能,包括生命周期、AOP/拦截、策略等。减轻组件负责管理其依赖项的责任。湾。提供在不同环境中交换依赖项实现的能力。 C。允许通过模拟依赖项来测试组件。 d。提供一种在整个应用程序中共享资源的机制。一个。在进行测试驱动开发时至关重要。如果没有 IoC,可能很难进行测试,因为被测组件与系统的其余部分高度耦合。湾。开发模块化系统时至关重要。模块化系统是其组件无需重新编译即可更换的系统。 C。如果有许多需要解决的横切关注点,尤其是在企业应用程序中,则至关重要。
我将写下我对这两个术语的简单理解:
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
假设我们在酒店开会。
我们邀请了很多人,所以我们遗漏了许多水壶和许多塑料杯。
当有人想喝水时,他/她装满一个杯子,喝完水,然后把杯子扔在地板上。
大约一个小时后,我们的地板上铺满了塑料杯和水。
让我们在反转控件后尝试:
想象一下在同一个地方举行同样的会议,但我们现在有一个服务员只拿着一个玻璃杯而不是塑料杯(单身)
当有人想喝酒时,服务员会给他们一杯。他们喝了它,然后把它还给服务员。
撇开卫生问题不谈,使用服务员(过程控制)更加有效和经济。
这正是 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
我找到了一个非常清晰的示例 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。
我同意 NilObject,但我想补充一点:
如果您发现自己复制了整个方法并且只更改了一小部分代码,您可以考虑通过控制反转来解决它
如果您发现自己在复制和粘贴代码,那么您几乎总是在做错某事。编纂为设计原则Once and Only Once。
例如,task#1 是创建对象。如果没有 IOC 概念,task#1 应该由 Programmer 完成。但是如果有 IOC 概念,task#1 将由容器完成。
简而言之,控制从程序员反转到容器。因此,它被称为控制反转。
我找到了一个很好的例子 here。
似乎“IoC”这个首字母缩写词和它所代表的名字最令人困惑的是它的名字太迷人了——几乎是一个噪音名字。
我们真的需要一个名称来描述过程驱动编程和事件驱动编程之间的区别吗?好的,如果我们需要,但我们是否需要选择一个全新的“比生命更重要”的名称,它让人困惑多于解决问题?
控制反转是当你去杂货店时,你的妻子给了你要购买的产品清单。
在编程术语中,她将回调函数 getProductList()
传递给您正在执行的函数 - doShopping()
。
它允许函数的用户定义它的某些部分,使其更加灵活。
getProductList()
你必须找到钱的来源时,意味着控制在你身边。在反转的情况下,她将控制,意味着她将提供购买的钱。
我知道这里已经给出了答案。但我仍然认为,关于控制反转的一些基础知识必须在这里详细讨论,以供未来的读者阅读。
控制反转 (IoC) 建立在一个非常简单的原则之上,称为好莱坞原则。它说,
不要打电话给我们,我们会打电话给你
这意味着不要去好莱坞实现你的梦想,如果你值得,那么好莱坞会找到你并让你的梦想成真。几乎是颠倒的,对吧?
现在,当我们讨论 IoC 的原理时,我们常常忘记好莱坞。对于国际奥委会来说,必须具备三个要素,一个好莱坞,一个你和一个像实现你的梦想的任务。
在我们的编程世界中,好莱坞代表一个通用框架(可能由您或其他人编写),您代表您编写的用户代码,任务代表您想用代码完成的事情。现在你永远不会自己去触发你的任务,而不是在 IoC 中!相反,您已经设计了所有内容,以便您的框架将为您触发您的任务。因此,您构建了一个可重用的框架,可以使某人成为英雄或使另一个人成为恶棍。但该框架始终负责,它知道何时选择某人,而某人只知道它想成为什么。
这里将给出一个真实的例子。假设您想开发一个 Web 应用程序。因此,您创建了一个框架来处理 Web 应用程序应该处理的所有常见事情,例如处理 http 请求、创建应用程序菜单、服务页面、管理 cookie、触发事件等。
然后你在你的框架中留下一些钩子,你可以在其中放置更多代码来生成自定义菜单、页面、cookies或记录一些用户事件等。在每个浏览器请求上,你的框架将运行并执行你的自定义代码,如果钩子然后返回它到浏览器。
所以,这个想法非常简单。与其创建一个控制一切的用户应用程序,不如先创建一个可重用的框架来控制一切,然后编写您的自定义代码并将其连接到框架以及时执行这些代码。
Laravel 和 EJB 就是这种框架的例子。
参考:
https://martinfowler.com/bliki/InversionOfControl.html
https://en.wikipedia.org/wiki/Inversion_of_control
控制反转是一个通用原则,而依赖注入将此原则实现为对象图构造的设计模式(即配置控制对象如何相互引用,而不是对象本身控制如何获取对另一个对象的引用)。
将控制反转视为一种设计模式,我们需要看看我们正在反转什么。依赖注入反转了对构建对象图的控制。如果用外行的话来说,控制反转意味着程序中控制流的变化。例如。在传统的独立应用程序中,我们有 main 方法,控制从那里传递给其他第三方库(如果我们使用了第三方库的函数),但是通过控制反转控制从第三方库代码转移到我们的代码,因为我们正在接受第三方图书馆的服务。但是在程序中还有其他方面需要反转——例如调用方法和线程来执行代码。
对于那些对控制反转更深入感兴趣的人,已经发表了一篇论文,概述了作为一种设计模式的控制反转的更完整图片(OfficeFloor:使用办公模式改进软件设计http://doi.acm.org/10.1145/2739011.2739013,可从 { 2})。
确定的是以下关系:
控制反转(对于方法)= 依赖(状态)注入 + 持续注入 + 线程注入
可用控制反转的上述关系摘要 - http://dzone.com/articles/inversion-of-coupling-control
我喜欢这个解释: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
更灵活,实现可以在运行时决定
代码单元隔离,测试更轻松
...
一个非常简单的书面解释可以在这里找到
http://binstock.blogspot.in/2008/01/excellent-explanation-of-dependency.html
它说 -
“任何重要的应用程序都由两个或多个相互协作以执行某些业务逻辑的类组成。传统上,每个对象都负责获取对与其协作的对象(其依赖项)的自己的引用。在应用 DI 时,对象在创建时由协调系统中每个对象的外部实体赋予它们的依赖关系。换句话说,依赖关系被注入到对象中。
IoC 是关于反转您的代码和第三方代码(库/框架)之间的关系:
在正常的软件开发中,您编写 main() 方法并调用“库”方法。你在控制:)
在 IoC 中,“框架”控制 main() 并调用您的方法。框架在控制中:(
DI(依赖注入)是关于控制如何在应用程序中流动的。传统的桌面应用程序具有从您的应用程序(main() 方法)到其他库方法调用的控制流,但是通过 DI 控制流反转,框架负责启动您的应用程序、初始化它并在需要时调用您的方法。
最后你总是赢:)
编程口语
简单来说 IoC:它是使用接口作为特定事物(如字段或参数)的一种方式,作为某些类可以使用的通配符。它允许代码的可重用性。
例如,假设我们有两个类:Dog 和 Cat。两者都有相同的品质/状态:年龄、大小、体重。因此,我可以创建一个名为 AnimalService 的服务类,而不是创建一个名为 DogService 和 CatService 的服务类,它仅允许在 Dog 和 Cat 使用接口 IAnimal 时使用它们。
但是,从务实的角度来说,它有一些倒退。
a) 大多数开发人员不知道如何使用它。例如,我可以创建一个名为 Customer 的类,然后我可以自动(使用 IDE 的工具)创建一个名为 ICustomer 的接口。因此,无论接口是否会被重用,找到一个充满类和接口的文件夹并不罕见。它被称为膨胀。有些人可能会争辩说“将来我们可能会使用它”。 :-|
b) 它有一些限制。例如,我们来谈谈Dog和Cat的情况,我想为狗添加一个新的服务(功能)。假设我要计算训练一只狗需要多少天(trainDays()
),对于猫来说它没用,不能训练猫(我在开玩笑)。
b.1) 如果我将 trainDays()
添加到服务 AnimalService 那么它也适用于猫,它根本无效。
b.2) 我可以在 trainDays()
中添加一个条件,用于评估使用哪个类。但它会彻底破坏 IoC。
b.3) 我可以为新功能创建一个名为 DogService 的新服务类。但是,它将增加代码的可维护性,因为我们将为 Dog 提供两类服务(具有相似的功能),这很糟糕。
控制反转是将控制从库转移到客户端。当我们谈论将函数值(lambda 表达式)注入(传递)到控制(更改)库函数行为的高阶函数(库函数)中时,它更有意义。
因此,这种模式的一个简单实现(具有巨大影响)是一个高阶库函数(它接受另一个函数作为参数)。库函数通过让客户端能够提供“控制”函数作为参数来转移对其行为的控制。
例如,“map”、“flatMap”等库函数是 IoC 实现。
当然,受限的 IoC 版本是例如布尔函数参数。客户端可以通过切换布尔参数来控制库函数。
将库依赖项(携带行为)注入库的客户端或框架也可以视为 IoC
由于该问题已经有很多答案,但没有一个显示反转控制术语的细分,我看到有机会给出更简洁和有用的答案。
控制反转是一种实现依赖反转原则 (DIP) 的模式。 DIP 声明如下: 1. 高级模块不应该依赖于低级模块。两者都应该依赖于抽象(例如接口)。 2. 抽象不应该依赖于细节。细节(具体实现)应该依赖于抽象。
控制反转分为三种类型:
接口反转提供者不应定义接口。相反,消费者应该定义接口,而提供者必须实现它。接口反转允许消除每次添加新提供者时修改消费者的必要性。
Flow Inversion 改变流的控制。例如,您有一个控制台应用程序,您在其中要求输入许多参数,并且在每个输入的参数之后您都必须按 Enter 键。您可以在这里应用 Flow Inversion 并实现一个桌面应用程序,用户可以选择参数输入的顺序,用户可以编辑参数,并且在最后一步,用户只需按一次 Enter。
创建反转可以通过以下模式实现:工厂模式、服务定位器和依赖注入。创建反转有助于消除类型之间的依赖关系,将依赖对象创建过程移到使用这些依赖对象的类型之外。为什么依赖关系不好?这里有几个例子:在你的代码中直接创建一个新对象会使测试更难;不重新编译就不可能更改程序集中的引用(违反 OCP 原则);你不能轻易地用 web 用户界面替换桌面用户界面。
我已经为此阅读了很多答案,但是如果有人仍然感到困惑并且需要一个超“外行术语”来解释 IoC,我的看法是:
想象一下父母和孩子互相交谈。
没有 IoC:
*家长:只有当我问你问题时,你才能说话,只有在我允许的情况下,你才能行动。
家长:也就是说,如果我不问你,你就不能问我能不能吃饭、玩耍、上厕所甚至睡觉。
家长:你想吃吗?
孩子:没有。
家长:好的,我会回来的。等等我。
孩子:(想玩,但家长没问,孩子什么都做不了)。
1小时后...
家长:我回来了。你想玩吗?
孩子:是的。
家长:已获得许可。
孩子:(终于可以玩了)。
这个简单的场景解释了控件以父级为中心。孩子的自由受到限制,很大程度上取决于父母的问题。孩子只有在被要求说话时才能说话,并且只有在获得许可时才能行动。
使用国际奥委会:
孩子现在可以提出问题,而家长可以回答并获得许可。简单来说就是控制反转!孩子现在可以随时自由提问,尽管在权限方面仍然依赖于父母,但他不依赖于说话/提问的方式。
从技术上讲,这与控制台/shell/cmd 与 GUI 交互非常相似。 (这是 Mark Harrison 的答案高于第二个最佳答案)。在控制台中,您依赖于向您询问/显示的内容,如果不先回答问题,您将无法跳转到其他菜单和功能;遵循严格的顺序流程。 (以编程方式,这就像一个方法/函数循环)。然而,使用 GUI,菜单和功能被布局,用户可以选择它需要的任何东西,从而拥有更多的控制和更少的限制。 (以编程方式,菜单在选择并发生操作时具有回调)。
在类中创建对象称为紧耦合,Spring 通过遵循设计模式(DI/IOC)来消除这种依赖关系。在构造函数中传递的类对象中,而不是在类中创建。此外,我们在构造函数中给出了超类引用变量来定义更通用的结构。
使用 IoC,您不会更新您的对象。您的 IoC 容器将执行此操作并管理它们的生命周期。
它解决了必须手动将一种对象的每个实例化更改为另一种的问题。
当您的功能可能会在未来发生变化或可能会因所使用的环境或配置而有所不同时,这是合适的。