当第一次遇到控制反转(IoC)时,它可能非常令人困惑。

这是怎么一回事?它解决了哪个问题?什么时候使用合适,什么时候不合适?


当前回答

我已经读了很多关于这一点的答案,但如果有人仍然感到困惑,需要一个额外的“外行术语”来解释IoC,我的看法是:

想象一个父母和孩子彼此交谈。

没有IoC:

*家长:我问你问题时你才能说话,我允许你时你才能行动。

家长:这意味着,如果我不问你,你就不能问我你能不能吃饭、玩、上厕所甚至睡觉。

家长:你想吃吗?

孩子:没有。

家长:好的,我会回来的。等我。

孩子:(想玩,但由于家长没有问题,孩子什么都不会做)。

1小时后。。。

家长:我回来了。你想玩吗?

孩子:是的。

父级:已授予权限。

孩子:(终于可以玩了)。

这个简单的场景解释了控件以父级为中心。孩子的自由受到限制,高度依赖于父母的问题。孩子只有在被要求说话时才能说话,只有在获得许可时才能行动。

使用IoC:

孩子现在有能力提出问题,家长可以回答并获得许可。简单地说就是控制颠倒了!孩子现在可以随时提问,尽管在权限方面仍然依赖于家长,但他不依赖于说话/提问的方式。

从技术上解释,这与console/shell/cmd与GUI交互非常相似。(这是马克·哈里森的答案,排名第二)。在控制台中,您依赖于向您询问/显示的内容,如果不先回答问题,您无法跳转到其他菜单和功能;遵循严格的顺序流程。(编程上这就像一个方法/函数循环)。然而,有了GUI,菜单和功能就被布置好了,用户可以选择所需的任何内容,从而拥有更多的控制权和更少的限制。(以编程方式,菜单在选中并执行操作时具有回调)。

其他回答

这里可以找到非常简单的书面解释

http://binstock.blogspot.in/2008/01/excellent-explanation-of-dependency.html

上面写着-

“任何非平凡的应用程序都由两个或多个类组成相互协作以执行一些业务逻辑。传统上,每个对象都负责获得自己的对其协作对象(其依赖项)的引用。应用DI时,对象在创建时被赋予其依赖性某个外部实体在系统换句话说,依赖项被注入到对象中。"

所以上面的数字1。什么是控制反转?维护是它为我解决的首要问题。它保证我使用的是接口,这样两个类就不会彼此亲密。

使用温莎城堡这样的容器,它可以更好地解决维护问题。能够在不更改一行代码的情况下,将一个连接到数据库的组件替换为一个使用基于文件的持久性的组件,这是非常棒的(配置更改完成了)。

一旦你进入泛型,它会变得更好。想象一下,拥有一个接收记录并发布消息的消息发布者。它不在乎它发布了什么,但它需要一个映射器将记录中的内容转换为消息。

public class MessagePublisher<RECORD,MESSAGE>
{
    public MessagePublisher(IMapper<RECORD,MESSAGE> mapper,IRemoteEndpoint endPointToSendTo)
    {
      //setup
    }
}

我写过一次,但现在如果我发布不同类型的消息,我可以向这组代码中注入许多类型。我还可以编写映射器,将相同类型的记录映射到不同的消息。将DI与Generics结合使用使我能够编写很少的代码来完成许多任务。

哦,是的,存在可测试性问题,但它们比IoC/DI的优势更为次要。

我绝对喜欢IoC/DI。

3.当你有一个中等规模的项目时,它会变得更加合适。我会说,当你开始感到疼痛时,它就变得合适了。

我在这里找到了一个非常清楚的例子,它解释了“控制是如何颠倒的”。

经典代码(无依赖注入)

以下是不使用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。

例如,任务#1是创建对象。没有IOC概念,任务#1应该由程序员完成。但有了IOC概念后,任务#1将由容器完成。

简而言之,控件从编程器转换为容器。因此,它被称为控制反转。

我在这里找到了一个很好的例子。

什么是控制反转?

如果您遵循这两个简单的步骤,您就完成了控制反转:

把该做什么和什么时候该做分开。确保零件何时尽可能少地了解什么零件;反之亦然。

根据您用于实现的技术/语言,这些步骤中的每一个都有几种可能的技术。

--

控制反转(IoC)的反转部分令人困惑;因为反转是相对项。了解IoC的最好方法是忘记这个词!

--

示例

事件处理。事件处理程序(做什么部分)--引发事件(何时做部分)依赖注入。构造依赖项(做什么部分)的代码——在需要时为客户机实例化和注入依赖项,这通常由Dagger等DI工具来处理(什么时候做部分)。接口。组件客户端(何时执行部分)--组件接口实现(执行部分)x装置夹具。Setup和TearDown(要做的部分)--xUnit框架在开始时调用Setup,在结束时调用TearDown(何时做部分)模板方法设计模式。模板方法何时执行部分--基本子类实现何时执行部分COM.DllMain、DllCanUnload等中的DLL容器方法(做什么部分)--COM/OS(什么时候做部分)