当第一次遇到控制反转(IoC)时,它可能非常令人困惑。
这是怎么一回事?它解决了哪个问题?什么时候使用合适,什么时候不合适?
当第一次遇到控制反转(IoC)时,它可能非常令人困惑。
这是怎么一回事?它解决了哪个问题?什么时候使用合适,什么时候不合适?
当前回答
假设你是一个物体。然后你去餐馆:
没有IoC:你要求“苹果”,当你要求更多时,你总是得到苹果。
与IoC:你可以要求“水果”。每次上桌你都可以得到不同的水果。例如,苹果、橙子或西瓜。
所以,很明显,当你喜欢品种时,IoC是首选。
其他回答
我在这里找到了一个非常清楚的例子,它解释了“控制是如何颠倒的”。
经典代码(无依赖注入)
以下是不使用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。
我将写下我对这两个术语的简单理解:
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:它是使用接口作为特定对象(例如字段或参数)的一种方式,作为某些类可以使用的通配符。它允许代码的可重用性。
例如,假设我们有两个类:狗和猫。两者具有相同的品质/状态:年龄、体型、体重。因此,我可以创建一个名为AnimalService的服务类,而不是创建一个称为DogService和CatService的服务,它只允许在Dog和Cat使用IAnimal接口时使用它们。
然而,从务实的角度来看,它有一些倒退。
a) 大多数开发人员不知道如何使用它。例如,我可以创建一个名为Customer的类,我可以(使用IDE的工具)自动创建一个称为ICustomer的接口。因此,无论接口是否会被重用,找到一个充满类和接口的文件夹并不罕见。它叫做BLOATED。有些人可能会认为“也许在未来我们可以使用它”-|
b) 它有一些限制。例如,让我们讨论一下Dog和Cat的情况,我想添加一个仅针对狗的新服务(功能)。比方说,我想计算训练一只狗所需的天数(trainDays()),因为猫没用,猫不能训练(我开玩笑)。
b.1)如果我将trainDays()添加到服务AnimalService中,那么它也适用于猫,并且根本无效。
b.2)我可以在trainDays()中添加一个条件,它评估使用的类。但这将彻底打破IoC。
b.3)我可以为新功能创建一个名为DogService的新服务类。但是,这将增加代码的可维护性,因为我们将为Dog提供两类服务(具有类似的功能),这很糟糕。
为了理解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/
真的不明白为什么会有很多错误的答案,甚至被接受的答案也不太准确,这让人很难理解。真相总是简单明了的。
正如@Schneider在@Mark Harrison的回答中所评论的,请阅读Martin Fowler关于IoC的帖子。
https://martinfowler.com/bliki/InversionOfControl.html
我最喜欢的是:
这种现象就是控制反转(也称为好莱坞原则——“不要打电话给我们,我们会打电话给你”)。
为什么?
IoC的Wiki,我可以引用一段话。
控制反转用于增加程序的模块性并使其可扩展。。。随后在2004年由Robert C.Martin和Martin Fowler进一步推广。
Robert C.Martin:《清洁代码:敏捷软件工艺手册》的作者。
马丁·福勒:《重构:改进现有代码的设计》一书的作者。