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

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


当前回答

IoC原则有助于设计松散耦合的类,使其可测试、可维护和可扩展。

其他回答

控制反转,(或IoC),是关于获得自由(你结婚了,失去了自由,你被控制了。你离婚了,你刚刚实现了控制反转。这就是我们所说的“脱钩”。好的计算机系统阻碍了一些非常亲密的关系。)更灵活(你办公室的厨房只供应干净的自来水,这是你想喝水时的唯一选择。你的老板通过安装一台新的咖啡机实现了控制反转。现在你可以灵活选择自来水或咖啡。)和更少的依赖性(你的伴侣有工作,你没有工作,你在经济上依赖你的伴侣,所以你受到控制。你找到了工作,你实现了控制反转。良好的计算机系统鼓励依赖。)

当你使用台式电脑时,你已经从(或者说,被控制)了。你必须坐在屏幕前看着屏幕。用键盘打字,用鼠标导航。一个写得不好的软件会让你更加痛苦。如果你把桌面换成笔记本电脑,那么你的控制就有点颠倒了。你可以轻松地拿着它四处走动。所以现在你可以用电脑控制你的位置,而不是由电脑控制。

通过实现控制反转,软件/对象消费者可以获得更多的软件/对象控制/选项,而不是被控制或拥有更少的选项。

考虑到上述想法。我们仍然错过了IoC的一个关键部分。在IoC的场景中,软件/对象使用者是一个复杂的框架。这意味着您创建的代码不是自己调用的。现在让我们来解释一下为什么这种方式对web应用程序更有效。

假设您的代码是一组工作人员。他们需要造一辆车。这些工人需要一个地方和工具(软件框架)来制造汽车。一个传统的软件框架就像一个有很多工具的车库。因此,工人们需要自己制定计划,并使用工具来制造汽车。造一辆车不是一件容易的事,工人们很难做好计划并进行适当的合作。一个现代化的软件框架将像一个拥有所有设施和管理人员的现代化汽车工厂。工人不必制定任何计划,管理者(框架的一部分,他们是最聪明的人,制定了最复杂的计划)将帮助协调,以便工人知道何时完成他们的工作(框架调用您的代码)。工人们只需要足够灵活地使用经理给他们的任何工具(通过使用依赖注入)。

尽管工人将项目管理的控制权交给了管理者(框架)。但有一些专业人士帮助是很好的。这就是IoC的真正来源。

具有MVC架构的现代Web应用程序依赖于框架来执行URL路由,并将控制器放置在适当的位置以供框架调用。

依赖注入和控制反转是相关的。依赖注入在微观层面,控制反转在宏观层面。为了完成一顿饭(实现IoC),你必须吃每一口(实现DI)。

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

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

没有IoC:

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

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

家长:你想吃吗?

孩子:没有。

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

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

1小时后。。。

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

孩子:是的。

父级:已授予权限。

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

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

使用IoC:

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

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

控制反转是将控制权从库转移到客户端。当我们讨论将函数值(lambda表达式)注入(传递)到控制(改变)库函数行为的高阶函数(库函数)中的客户端时,它更有意义。

因此,这个模式的一个简单实现(具有巨大的含义)是一个更高阶的库函数(它接受另一个函数作为参数)。库函数通过赋予客户端提供“控制”函数作为参数的能力来传递对其行为的控制。

例如,“map”、“flatMap”等库函数是IoC实现。

当然,例如,有限的IoC版本是布尔函数参数。客户端可以通过切换布尔参数来控制库函数。

将库依赖项(承载行为)注入到库中的客户端或框架也可以被视为IoC

控制反转(IoC)模式是关于提供任何类型的回调,它“实现”和/或控制反应,而不是直接执行自己(换句话说,反转和/或将控制重定向到外部处理器/控制器)。依赖注入(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 SpellCheckr();),这意味着TextEditor类直接依赖于SpellChecker类。

在第二个代码示例中,我们通过在TextEditor的构造函数签名中使用SpellChecker依赖类(而不是在类中初始化依赖项)来创建抽象。这允许我们调用依赖项,然后将其传递给TextEditor类,如下所示:

SpellChecker sc = new SpellChecker(); // dependency
TextEditor textEditor = new TextEditor(sc);

现在,创建TextEditor类的客户端可以控制使用哪个SpellChecker实现,因为我们正在将依赖项注入TextEditor签名中。


注意,就像IoC是许多其他模式的基础一样,上面的示例只是依赖注入类型中的一种,例如:

构造函数注入。IocSpellChecker的实例将自动传递给构造函数,或手动传递给构造函数。沉淀剂注入。IocSpellChecker的实例将通过setter方法或公共属性传递。服务查找和/或服务定位器其中TextEditor将向已知的提供者请求IocSpellChecker类型的全局使用的实例(服务)(这可能不存储所述实例,而是一次又一次地询问提供者)。

控制反转意味着您控制组件(类)的行为。为什么称之为“反转”,因为在这种模式之前,类是硬连线的,并且确定了它们将要做什么。

导入具有TextEditor和SpellChecker类的库。现在,这个拼写检查器自然只检查英语的拼写。假设您希望TextEditor处理德语并能够进行拼写检查,那么您可以控制它。

在IoC的情况下,这种控制是反向的,即它是如何给你的?库将实现如下内容:

它将有一个TextEditor类,然后会有一个ISpellChecker(它是一个接口,而不是一个具体的SpellChecker类),当您在IoC容器中配置东西时,例如Spring,您可以提供自己的“ISpellChecker”实现,它将检查德语的拼写。因此,拼写检查将如何工作的控制权是从该库中获取并交给您的。这是IoC。