当第一次遇到控制反转(IoC)时,它可能非常令人困惑。
这是怎么一回事?它解决了哪个问题?什么时候使用合适,什么时候不合适?
当第一次遇到控制反转(IoC)时,它可能非常令人困惑。
这是怎么一回事?它解决了哪个问题?什么时候使用合适,什么时候不合适?
当前回答
IoC是关于颠倒代码和第三方代码(库/框架)之间的关系:
在正常的软件开发中,您编写main()方法并调用“library”方法。您可以控制:)在IoC中,“框架”控制main()并调用您的方法。该框架处于受控状态:(
DI(依赖注入)是关于控件在应用程序中如何流动的。传统的桌面应用程序具有从应用程序(main()方法)到其他库方法调用的控制流,但DI控制流是反向的,框架负责启动应用程序、初始化应用程序并在需要时调用方法。
最终,你总会赢:)
其他回答
使用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。
但我认为你必须非常小心。如果你过度使用这种模式,你会做出非常复杂的设计,甚至更复杂的代码。
就像这个例子中的TextEditor一样:如果你只有一个拼写检查器,那么可能真的没有必要使用IoC?除非你需要写单元测试之类的。。。
无论如何:要讲道理。设计模式是很好的实践,但不是圣经。不要把它粘在任何地方。
我已经读了很多关于这一点的答案,但如果有人仍然感到困惑,需要一个额外的“外行术语”来解释IoC,我的看法是:
想象一个父母和孩子彼此交谈。
没有IoC:
*家长:我问你问题时你才能说话,我允许你时你才能行动。
家长:这意味着,如果我不问你,你就不能问我你能不能吃饭、玩、上厕所甚至睡觉。
家长:你想吃吗?
孩子:没有。
家长:好的,我会回来的。等我。
孩子:(想玩,但由于家长没有问题,孩子什么都不会做)。
1小时后。。。
家长:我回来了。你想玩吗?
孩子:是的。
父级:已授予权限。
孩子:(终于可以玩了)。
这个简单的场景解释了控件以父级为中心。孩子的自由受到限制,高度依赖于父母的问题。孩子只有在被要求说话时才能说话,只有在获得许可时才能行动。
使用IoC:
孩子现在有能力提出问题,家长可以回答并获得许可。简单地说就是控制颠倒了!孩子现在可以随时提问,尽管在权限方面仍然依赖于家长,但他不依赖于说话/提问的方式。
从技术上解释,这与console/shell/cmd与GUI交互非常相似。(这是马克·哈里森的答案,排名第二)。在控制台中,您依赖于向您询问/显示的内容,如果不先回答问题,您无法跳转到其他菜单和功能;遵循严格的顺序流程。(编程上这就像一个方法/函数循环)。然而,有了GUI,菜单和功能就被布置好了,用户可以选择所需的任何内容,从而拥有更多的控制权和更少的限制。(以编程方式,菜单在选中并执行操作时具有回调)。
维基百科文章。对我来说,控制反转就是将您按顺序编写的代码转换为委托结构。您的程序不是显式地控制一切,而是设置一个类或库,其中包含发生某些事情时要调用的某些函数。它解决了代码重复。例如,在过去,您可以手动编写自己的事件循环,在系统库中轮询新事件。现在,大多数现代API只需告诉系统库您感兴趣的事件,它会让您知道它们何时发生。控制反转是减少代码重复的一种实用方法,如果您发现自己复制了整个方法,只更改了一小段代码,可以考虑使用控制反转来解决它。在许多语言中,通过委托、接口甚至原始函数指针的概念,控制反转变得容易。它并不适合在所有情况下使用,因为这样编写时,程序的流程可能更难遵循。在编写可重用的库时,这是一种设计方法的有用方法,但除非它真的解决了代码重复问题,否则应该在自己程序的核心中谨慎使用。