第一次遇到控制反转 (IoC) 时可能会非常混乱。
- 它是什么?
- 它解决了哪个问题?
- 什么时候适合使用,什么时候不适合?
第一次遇到控制反转 (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
该类。
在第二个代码示例中,我们通过在的构造函数签名中使用SpellChecker
依赖类来创建抽象(而不是在类中初始化依赖)。TextEditor
这允许我们调用依赖项,然后将其传递给 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 的最好方法就是忘记这个词!
--
例子
控制反转是关于分离关注点。
没有 IoC:你有一台笔记本电脑,但不小心打破了屏幕。而且,您会发现市场上没有相同型号的笔记本电脑屏幕。所以你被困住了。
使用 IoC:您有一台台式计算机,但您不小心打破了屏幕。你会发现你几乎可以从市场上买到任何桌面显示器,而且它与你的桌面配合得很好。
在这种情况下,您的桌面成功实现了 IoC。它接受各种类型的显示器,而笔记本电脑不接受,它需要一个特定的屏幕来固定。
控制反转(或 IoC)是关于获得自由(你结婚了,你失去了自由,你被控制了。你离婚了,你刚刚实施了控制反转。这就是我们所说的“解耦”。好的计算机系统不鼓励一些非常亲密的关系。)更多的灵活性(你办公室的厨房只提供干净的自来水,这是你想喝的唯一选择。你的老板通过设置一台新咖啡机实施了控制反转。现在你得到了选择自来水或咖啡的灵活性。)和更少的依赖(你的伴侣有工作,你没有工作,你在经济上依赖你的伴侣,所以你被控制了。你找到了工作,你实施了控制反转。好的计算机系统鼓励独立。)
当您使用台式计算机时,您已经从属(或者说,被控制)。你必须坐在屏幕前看着它。使用键盘键入并使用鼠标导航。一个写得不好的软件会更加奴役你。如果你用笔记本电脑代替你的台式机,那么你的控制就有点颠倒了。您可以轻松携带它并四处走动。因此,现在您可以使用计算机控制您所在的位置,而不是您的计算机控制它。
通过实施控制反转,软件/对象消费者可以获得对软件/对象的更多控制/选项,而不是被控制或拥有更少的选项。
带着以上的想法。我们仍然错过了 IoC 的一个关键部分。在 IoC 场景中,软件/对象消费者是一个复杂的框架。这意味着您创建的代码不是您自己调用的。现在让我们解释一下为什么这种方式更适合 Web 应用程序。
假设您的代码是一组工人。他们需要造一辆汽车。这些工人需要一个地方和工具(一个软件框架)来制造汽车。一个传统的软件框架就像一个有很多工具的车库。所以工人们需要自己制定计划,并使用工具来制造汽车。造车不是一件容易的事,工人们要好好规划和配合,真的很难。一个现代软件框架将像一个现代化的汽车工厂,所有的设施和管理人员都到位。工人不必制定任何计划,经理(框架的一部分,他们是最聪明的人并制定了最复杂的计划)将帮助协调,以便工人知道何时完成他们的工作(框架调用您的代码)。工作人员只需要足够灵活以使用经理提供给他们的任何工具(通过使用依赖注入)。
尽管工作人员将顶层项目管理的控制权交给了管理者(框架)。但是最好有一些专业人士的帮助。这就是IoC的概念真正的来历。
具有 MVC 架构的现代 Web 应用程序依赖于框架来执行 URL 路由并将控制器放置在适当的位置以供框架调用。
依赖注入和控制反转是相关的。依赖注入是微观层面的,控制反转是宏观层面的。您必须吃完每一口食物(实施 DI)才能完成一顿饭(实施 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
.
通过实施控制反转,软件/对象消费者可以获得对软件/对象的更多控制/选项,而不是被控制或拥有更少的选项。
控制反转作为设计指南的目的如下:
某个任务的执行与执行是脱钩的。
每个模块都可以专注于它的设计目的。
模块不对其他系统做什么做任何假设,而是依赖于它们的合同。
替换模块对其他模块没有副作用
我将在这里保持抽象,您可以访问以下链接以详细了解该主题。
一个很好的例子
假设我们在某家酒店开会。
许多人,许多水壶,许多塑料杯。
当有人想喝的时候,他/她装满一个杯子,喝完,然后把杯子扔在地板上。
一个小时左右后,我们的地板上铺满了塑料杯和水。
现在让我们反转控制。
在同一个地方举行同一次会议,但我们有一个服务员拿着一个玻璃杯而不是塑料杯(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
}
有用的链接:-
我在这里找到了一个非常清楚的例子,它解释了“控制是如何倒置的”。
经典代码(无依赖注入)
以下是不使用 DI 的代码大致如何工作:
使用依赖注入
以下是使用 DI 的代码大致如何工作:
依赖关系的控制从一个被调用反转到一个调用。
它解决了哪些问题?
依赖注入可以很容易地与注入类的不同实现进行交换。在进行单元测试时,您可以注入一个虚拟实现,这使测试变得更加容易。
例如:假设您的应用程序将用户上传的文件存储在 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 将由容器完成。
简而言之,控制从程序员反转到容器。因此,它被称为控制反转。
我在这里找到了一个很好的例子。
似乎“IoC”这个首字母缩写词和它所代表的名字最令人困惑的是它的名字太迷人了——几乎是一个噪音名字。
我们真的需要一个名称来描述过程驱动编程和事件驱动编程之间的区别吗?好的,如果我们需要,但我们是否需要选择一个全新的“比生命更重要”的名称,它让人困惑多于解决问题?
控制反转是当你去杂货店时,你的妻子给了你要购买的产品清单。
在编程术语中,她将一个回调函数传递给getProductList()
您正在执行的函数 - doShopping()
。
它允许函数的用户定义它的某些部分,使其更加灵活。
控制反转是一个通用原则,而依赖注入将此原则实现为对象图构造的设计模式(即配置控制对象如何相互引用,而不是对象本身控制如何获取对另一个对象的引用)。
将控制反转视为一种设计模式,我们需要看看我们正在反转什么。依赖注入反转了对构建对象图的控制。如果用外行的话来说,控制反转意味着程序中控制流的变化。例如。在传统的独立应用程序中,我们有 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 的原理时,我们常常忘记好莱坞。对于国际奥委会来说,必须具备三个要素,一个好莱坞,一个你和一个像实现你的梦想的任务。
在我们的编程世界中,好莱坞代表一个通用框架(可能由您或其他人编写),您代表您编写的用户代码,任务代表您想用代码完成的事情。现在你永远不会自己去触发你的任务,而不是在 IoC 中!相反,您已经设计了所有内容,以便您的框架将为您触发您的任务。因此,您构建了一个可重用的框架,可以使某人成为英雄或使另一个人成为恶棍。但该框架始终负责,它知道何时选择某人,而某人只知道它想成为什么。
这里将给出一个真实的例子。假设您想开发一个 Web 应用程序。因此,您创建了一个框架来处理 Web 应用程序应该处理的所有常见事情,例如处理 http 请求、创建应用程序菜单、服务页面、管理 cookie、触发事件等。
然后你在你的框架中留下一些钩子,你可以在其中放置更多代码来生成自定义菜单、页面、cookies或记录一些用户事件等。在每个浏览器请求上,你的框架将运行并执行你的自定义代码,如果钩子然后返回它到浏览器。
所以,这个想法非常简单。与其创建一个控制一切的用户应用程序,不如先创建一个可重用的框架来控制一切,然后编写您的自定义代码并将其连接到框架以及时执行这些代码。
Laravel 和 EJB 就是这种框架的例子。
参考:
一个非常简单的书面解释可以在这里找到
http://binstock.blogspot.in/2008/01/excellent-explanation-of-dependency.html
它说 -
“任何重要的应用程序都由两个或多个相互协作以执行某些业务逻辑的类组成。传统上,每个对象都负责获取对与其协作的对象(其依赖项)的自己的引用。在应用 DI 时,对象在创建时由协调系统中每个对象的外部实体赋予它们的依赖关系。换句话说,依赖关系被注入到对象中。
我喜欢这个解释: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 的东西。
阅读文章以获取更多信息和好处描述,例如:
...
IoC 是关于反转您的代码和第三方代码(库/框架)之间的关系:
DI(依赖注入)是关于控制如何在应用程序中流动的。传统的桌面应用程序具有从您的应用程序(main() 方法)到其他库方法调用的控制流,但是通过 DI 控制流反转,框架负责启动您的应用程序、初始化它并在需要时调用您的方法。
最后你总是赢:)
编程口语
简单来说 IoC:它是使用接口作为特定事物(如字段或参数)的一种方式,作为某些类可以使用的通配符。它允许代码的可重用性。
例如,假设我们有两个类:Dog和Cat。两者都有相同的品质/状态:年龄、大小、体重。因此,我可以创建一个名为AnimalService的服务类,而不是创建一个名为DogService和CatService的服务类,它仅在 Dog 和 Cat 使用接口IAnimal时才允许使用它们。
但是,从务实的角度来说,它有一些倒退。
a)大多数开发人员不知道如何使用它。例如,我可以创建一个名为Customer的类,然后我可以自动(使用 IDE 的工具)创建一个名为ICustomer的接口。因此,无论接口是否会被重用,找到一个充满类和接口的文件夹并不罕见。它被称为膨胀。有些人可能会争辩说“将来我们可能会使用它”。:-|
b) 它有一些限制。例如,让我们谈谈Dog和Cat的情况,我想为狗添加一个新的服务(功能)。假设我想计算我需要训练一只狗的天数trainDays()
(
b.1)如果我添加trainDays()
到 Service 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 容器将执行此操作并管理它们的生命周期。
它解决了必须手动将一种对象的每个实例化更改为另一种的问题。
当您的功能可能会在未来发生变化或可能会因所使用的环境或配置而有所不同时,这是合适的。
为了理解这个概念,控制反转 (IoC) 或依赖反转原则 (DIP) 涉及两个活动:抽象和反转。依赖注入(DI)只是为数不多的倒置方法之一。
要了解有关此的更多信息,您可以阅读我的博客 Here
这是一种让实际行为来自边界之外的做法(面向对象编程中的类)。边界实体只知道它的抽象(例如接口、抽象类、面向对象编程中的委托)。
在编程方面,IoC 尝试通过模块化、解耦各个部分并使其可单元测试来解决单体代码。
大多数情况下它是合适的,除非你有只需要单片代码的情况(例如非常简单的程序)
在使用像 Castle Windsor 这样的容器时,它可以更好地解决维护问题。能够在不更改一行代码的情况下将进入数据库的组件换成使用基于文件的持久性的组件真是太棒了(配置更改,你已经完成了)。
一旦你进入泛型,它会变得更好。想象一下,有一个消息发布者接收记录并发布消息。它不关心它发布了什么,但它需要一个映射器来将某些内容从记录转换为消息。
public class MessagePublisher<RECORD,MESSAGE>
{
public MessagePublisher(IMapper<RECORD,MESSAGE> mapper,IRemoteEndpoint endPointToSendTo)
{
//setup
}
}
我曾经写过一次,但是现在如果我发布不同类型的消息,我可以在这组代码中注入多种类型。我还可以编写映射器来获取相同类型的记录并将它们映射到不同的消息。将 DI 与泛型结合使用使我能够编写很少的代码来完成许多任务。
哦,是的,存在可测试性问题,但它们仅次于 IoC/DI 的好处。
我绝对喜欢 IoC/DI。
3. 当你有一个更复杂的中型项目时,它就变得更合适了。我会说当你开始感到疼痛的那一刻就变得合适了。
真的不明白为什么会有很多错误的答案,甚至接受的答案也不太准确,这让事情变得难以理解。真相总是简单而干净的。
正如@Schneider在@Mark Harrison 的回答中评论的那样,请阅读Martin Fowler 讨论IoC 的帖子。
https://martinfowler.com/bliki/InversionOfControl.html
我最喜欢的一个是:
这种现象就是控制反转(也称为好莱坞原则——“不要打电话给我们,我们会打电话给你”)。
为什么?
IoC 的 Wiki,我可能会引用一个片段。
控制反转用于增加程序的模块化并使其可扩展……然后在 2004 年由Robert C. Martin和Martin Fowler进一步推广。
罗伯特 C. 马丁: 的作者<<Clean Code: A Handbook of Agile Software Craftsmanship>>
。
马丁·福勒: 的作者<<Refactoring: Improving the Design of Existing Code>>
。
控制反转意味着您控制组件(类)的行为方式。为什么将其称为“反转”,因为在此模式之前,这些类是硬连线的,并且对它们将要做什么是确定的,例如
您导入一个具有 aTextEditor
和SpellChecker
类的库。现在这自然SpellChecker
只会检查英语的拼写。假设如果您想要TextEditor
处理德语并能够进行拼写检查,您可以控制它。
使用 IoC,这个控制是倒置的,即它给了你,如何?该库将实现如下内容:
它将有一个TextEditor
类,然后它会有一个ISpeallChecker
(这是一个接口而不是一个具体的SpellChecker
类),当您在 IoC 容器(例如 Spring)中配置内容时,您可以提供自己的“ISpellChecker”实现,它将检查德语的拼写。因此,拼写检查如何工作的控制权是从该库中获取并交给您的。那是国际奥委会。
它是什么? (耦合)控制反转,改变方法签名的耦合方向。使用反向控制,方法签名的定义由方法实现(而不是方法的调用者)决定。完整的解释在这里
它解决了哪个问题? 自上而下的方法耦合。这随后消除了重构的需要。
什么时候适合使用,什么时候不适合? 对于不会发生太大变化的小型定义良好的应用程序,这可能是一种开销。但是,对于将要发展的定义较少的应用程序,它减少了方法签名的固有耦合。这给了开发人员更多的自由来发展应用程序,避免了对代码进行昂贵的重构的需要。基本上,允许应用程序在几乎没有返工的情况下发展。
要理解 IoC,我们应该谈谈依赖倒置。
依赖倒置:依赖于抽象,而不是具体。
控制反转:主要与抽象,以及主要如何成为系统的粘合剂。
我写了一些很好的例子,你可以在这里查看它们:
https://coderstower.com/2019/03/26/dependency-inversion-why-you-shouldnt-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/
IoC 原则有助于设计松散耦合的类,使它们可测试、可维护和可扩展。
控制反转是程序中责任转移的一个指标。
每次授予依赖项直接作用于调用方空间的能力时,都会发生控制反转。
最小的 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、设置事件、输出小部件等。) 为你。