已经发布了几个关于依赖注入的具体问题,例如何时使用它以及它有什么框架,
什么是依赖注入,何时/为什么应该或不应该使用它?
已经发布了几个关于依赖注入的具体问题,例如何时使用它以及它有什么框架,
什么是依赖注入,何时/为什么应该或不应该使用它?
当前回答
流行的答案毫无用处,因为它们以一种无用的方式定义依赖注入。让我们同意,“依赖性”是指我们的对象X所需要的一些预先存在的其他对象。但当我们说
$foo = Foo->new($bar);
我们只调用将参数传递到构造函数中。自从构造器被发明以来,我们一直在定期这样做。
“依赖注入”被认为是“控制反转”的一种类型,这意味着某些逻辑被从调用者中取出。当调用者传入参数时,情况并非如此,因此如果是DI,DI就不会意味着控制反转。
DI意味着在调用者和构造函数之间有一个中间层来管理依赖关系。Makefile是依赖注入的一个简单示例。“调用者”是在命令行上键入“make bar”的人,“构造函数”是编译器。Makefile指定bar依赖于foo,并执行
gcc -c foo.cpp; gcc -c bar.cpp
在执行
gcc foo.o bar.o -o bar
键入“makebar”的人不需要知道bar依赖于foo。在“makebar”和gcc之间注入了依赖关系。
中间层的主要目的不仅仅是将依赖项传递给构造函数,而是在一个地方列出所有依赖项,并向编码器隐藏它们(而不是让编码器提供它们)。
通常,中间层为构造的对象提供工厂,这些对象必须提供每个请求的对象类型都必须满足的角色。这是因为通过拥有一个隐藏构建细节的中间层,您已经受到了工厂施加的抽象惩罚,所以您不妨使用工厂。
其他回答
依赖注入意味着一种方式(实际上是任何方式),代码的一部分(例如类)可以以模块化的方式访问依赖项(代码的其他部分,例如它所依赖的其他类),而不必对它们进行硬编码(因此它们可以自由更改或重写,甚至可以根据需要在其他时间加载)
(顺便说一句,是的,对于一个相当简单的概念,它已经成为一个过度炒作的25美元的名字),我的25美分
公认的答案是一个好答案——但我想补充一点,DI非常像代码中避免硬编码常量的经典做法。
当您使用诸如数据库名称之类的常量时,您可以将其从代码内部快速移动到某个配置文件,并将包含该值的变量传递到需要它的位置。这样做的原因是,这些常量通常比代码的其他部分更频繁地更改。例如,如果您想在测试数据库中测试代码。
在面向对象编程的世界中,DI与此类似。那里的值而不是常量文字是整个对象-但是将创建它们的代码从类代码中移出的原因是相似的-对象的更改比使用它们的代码更频繁。一个重要的情况是需要进行这样的改变,那就是测试。
让我们想象一下,你想去钓鱼:
没有依赖注入,你需要自己处理所有的事情。你需要找一艘船,买一根鱼竿,寻找诱饵等等。当然,这是可能的,但这给你带来了很多责任。在软件方面,这意味着你必须对所有这些东西进行查找。通过依赖注入,其他人负责所有准备工作,并为您提供所需的设备。你将收到(“被注射”)船、鱼竿和鱼饵——所有这些都准备好使用。
依赖注入是将依赖传递给其他对象或框架(依赖注入器)。
依赖注入使测试更容易。注入可以通过构造函数完成。
SomeClass()的构造函数如下:
public SomeClass() {
myObject = Factory.getObject();
}
问题:如果myObject涉及诸如磁盘访问或网络访问之类的复杂任务,则很难在SomeClass()上进行单元测试。程序员必须模拟myObject,并可能拦截工厂调用。
替代解决方案:
将myObject作为参数传入构造函数
public SomeClass (MyClass myObject) {
this.myObject = myObject;
}
myObject可以直接传递,这使得测试更容易。
一种常见的替代方法是定义一个不做任何事情的构造函数。依赖注入可以通过setter完成。(h/t@MikeVella)。Martin Fowler记录了第三种选择(h/t@MarcDix),其中类显式地实现了程序员希望注入的依赖项的接口。
在没有依赖注入的情况下,很难在单元测试中隔离组件。
2013年,当我写下这个答案时,这是谷歌测试博客的一个主要主题。这对我来说仍然是最大的优势,因为程序员在运行时设计中并不总是需要额外的灵活性(例如,服务定位器或类似模式)。程序员通常需要在测试期间隔离类。
到目前为止,我找到的最好的定义是詹姆斯·肖尔的定义:
“依赖注入”是25美元5美分概念的术语。[...]依赖注入意味着对象的实例变量。[...].
马丁·福勒的一篇文章可能也很有用。
依赖注入基本上是提供对象所需的对象(其依赖项),而不是让它自己构造它们。这是一种非常有用的测试技术,因为它允许对依赖项进行嘲笑或清除。
依赖关系可以通过多种方式注入到对象中(例如构造函数注入或setter注入)。甚至可以使用专门的依赖注入框架(例如Spring)来实现这一点,但它们肯定不是必需的。您不需要这些框架进行依赖注入。显式实例化和传递对象(依赖项)与框架注入一样好。