当第一次遇到控制反转(IoC)时,它可能非常令人困惑。
这是怎么一回事?它解决了哪个问题?什么时候使用合适,什么时候不合适?
当第一次遇到控制反转(IoC)时,它可能非常令人困惑。
这是怎么一回事?它解决了哪个问题?什么时候使用合适,什么时候不合适?
当前回答
控制反转是一个通用原则,而依赖注入将这一原则实现为对象图构造的设计模式(即配置控制对象如何相互引用,而不是对象本身控制如何获取对另一个对象的引用)。
将控制反转视为一种设计模式,我们需要看看我们正在反转什么。依赖注入反转了对构建对象图的控制。如果用外行的术语来说,控制反转意味着程序中控制流的改变。例如,在传统的独立应用程序中,我们有一个主要的方法,从那里控制权被传递给其他第三方库(在这种情况下,我们使用了第三方的库的功能),但通过控制反转,控制权从第三方程序库代码转移到我们的代码,因为我们正在使用第三方代码库的服务。但在程序中还有其他方面需要反转,例如调用方法和线程来执行代码。
对于那些对控制反转感兴趣的人来说,已经发表了一篇论文,概述了控制反转作为一种设计模式的更完整的图景(OfficeFloor:使用办公模式来改进软件设计http://doi.acm.org/10.1145/2739011.2739013免费下载http://www.officefloor.net/about.html).
确定的关系如下:
控制反转(用于方法)=依赖(状态)注入+连续注入+线程注入
可用控制反转的上述关系汇总http://dzone.com/articles/inversion-of-coupling-control
其他回答
假设我们在酒店开会。
我们邀请了很多人,所以我们漏掉了很多壶水和很多塑料杯。
当有人想喝水时,他/她将杯子装满,喝水,然后将杯子扔在地板上。
大约一个小时后,我们的地板上覆盖着塑料杯和水。
让我们在反转控件后尝试:
想象一下,在同一地点举行同一次会议,但我们现在有一个服务员只带一个玻璃杯,而不是塑料杯(Singleton)
当有人想喝酒时,服务员会给他们一杯。他们把它喝了,然后还给服务员。
抛开卫生问题不谈,使用服务员(过程控制)更有效、更经济。
这正是Spring(另一个IoC容器,例如:Guice)所做的。Spring IoC没有让应用程序使用新的关键字(例如,拿一个塑料杯子)创建所需的东西,而是为应用程序提供所需对象(一杯水)的同一杯子/实例(singleton)。
把自己想象成这样一个会议的组织者:
示例:-
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.htmlhttp://martinfowler.com/articles/injection.htmlhttp://www.shawn-barrett.com/blog/post/Tip-of-the-day-e28093-Inversion-Of-Control.aspx
我在这里找到了一个非常清楚的例子,它解释了“控制是如何颠倒的”。
经典代码(无依赖注入)
以下是不使用DI的代码大致工作原理:
应用程序需要Foo(例如控制器),因此:应用程序创建Foo应用程序调用FooFoo需要Bar(例如服务),因此:Foo创建BarFoo调用BarBar需要Bim(服务、存储库…),因此:条形图创建Bim酒吧有点事
使用依赖注入
以下是使用DI的代码大致工作原理:
应用程序需要Foo,需要Bar,需要Bim,因此:应用程序创建Bim应用程序创建Bar并赋予它Bim应用程序创建Foo并给它Bar应用程序调用FooFoo调用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(或根据测试要求的任何预定义值)。
相反,如果您有一个控制器类来构造具有如下新关键字的存储对象:
class SomeController
{
private $storage;
function __construct()
{
$this->storage = new GoogleDriveService();
}
public function myFunction ()
{
return $this->storage->getFile($fileName);
}
}
当您想要使用Dropbox实现进行更改时,必须替换构建新GoogleDriveService对象的所有行,并使用DropboxService。此外,在测试SomeController类时,构造函数总是期望GoogleDriveService类,并触发该类的实际方法。
什么时候合适,什么时候不合适?在我看来,当您认为类有(或可能有)替代实现时,您可以使用DI。
控制反转是关于分离关注点。
没有IoC:你有一台笔记本电脑,你不小心弄坏了屏幕。糟糕的是,你发现市场上没有同一型号的笔记本电脑屏幕。所以你被卡住了。
IoC:你有一台台式电脑,你不小心把屏幕弄坏了。你发现你可以从市场上买到几乎所有的桌面显示器,而且它与你的桌面很好地配合。
在这种情况下,您的桌面成功地实现了IoC。它接受各种类型的显示器,而笔记本电脑不接受,它需要一个特定的屏幕来固定。
真的不明白为什么会有很多错误的答案,甚至被接受的答案也不太准确,这让人很难理解。真相总是简单明了的。
正如@Schneider在@Mark Harrison的回答中所评论的,请阅读Martin Fowler关于IoC的帖子。
https://martinfowler.com/bliki/InversionOfControl.html
我最喜欢的是:
这种现象就是控制反转(也称为好莱坞原则——“不要打电话给我们,我们会打电话给你”)。
为什么?
IoC的Wiki,我可以引用一段话。
控制反转用于增加程序的模块性并使其可扩展。。。随后在2004年由Robert C.Martin和Martin Fowler进一步推广。
Robert C.Martin:《清洁代码:敏捷软件工艺手册》的作者。
马丁·福勒:《重构:改进现有代码的设计》一书的作者。
控制反转意味着您控制组件(类)的行为。为什么称之为“反转”,因为在这种模式之前,类是硬连线的,并且确定了它们将要做什么。
导入具有TextEditor和SpellChecker类的库。现在,这个拼写检查器自然只检查英语的拼写。假设您希望TextEditor处理德语并能够进行拼写检查,那么您可以控制它。
在IoC的情况下,这种控制是反向的,即它是如何给你的?库将实现如下内容:
它将有一个TextEditor类,然后会有一个ISpellChecker(它是一个接口,而不是一个具体的SpellChecker类),当您在IoC容器中配置东西时,例如Spring,您可以提供自己的“ISpellChecker”实现,它将检查德语的拼写。因此,拼写检查将如何工作的控制权是从该库中获取并交给您的。这是IoC。