已经发布了几个关于依赖注入的具体问题,例如何时使用它以及它有什么框架,

什么是依赖注入,何时/为什么应该或不应该使用它?


当前回答

什么是依赖注入?

依赖注入(DI)意味着分离彼此依赖的对象。假设对象A依赖于对象B,因此想法是将这些对象彼此分离。我们不需要使用new关键字对对象进行硬编码,而是在运行时共享对对象的依赖关系,而不管编译时间如何。如果我们谈论

依赖注入在Spring中的工作原理:

我们不需要使用new关键字硬编码对象,而是在配置文件中定义bean依赖关系。弹簧容器将负责连接所有部件。

控制反转(IOC)

IOC是一个通用概念,可以用多种不同的方式表达,依赖注入是IOC的一个具体例子。

两种类型的依赖注入:

构造器注入沉淀剂注入

1.基于构造函数的依赖注入:

当容器调用具有多个参数的类构造函数时,就完成了基于构造函数的DI,每个参数表示对其他类的依赖。

public class Triangle {

private String type;

public String getType(){
    return type;
 }

public Triangle(String type){   //constructor injection
    this.type=type;
 }
}
<bean id=triangle" class ="com.test.dependencyInjection.Triangle">
        <constructor-arg value="20"/>
  </bean>

2.基于Setter的依赖注入:

基于Setter的DI是在调用无参数构造函数或无参数静态工厂方法来实例化bean之后,通过容器调用bean上的Setter方法来实现的。

public class Triangle{

 private String type;

 public String getType(){
    return type;
  }
 public void setType(String type){          //setter injection
    this.type = type;
  }
 }

<!-- setter injection -->
 <bean id="triangle" class="com.test.dependencyInjection.Triangle">
        <property name="type" value="equivialteral"/>

注:对于强制依赖项使用构造函数参数,对于可选依赖项使用setter,这是一个很好的经验法则。注意,如果我们在setter上使用基于@Required的注释,则可以将setter作为必需的依赖项。

其他回答

依赖注入(DI)的全部目的是保持应用程序源代码干净和稳定:

清除依赖项初始化代码无论使用的依赖关系如何稳定

实际上,每个设计模式都将关注点分开,以使将来的更改影响最小的文件。

DI的特定域是依赖配置和初始化的委托。

示例:带有shell脚本的DI

如果您偶尔在Java之外工作,请回想一下源代码在许多脚本语言(Shell、Tcl等,甚至在Python中被误用)中的使用情况。

考虑简单的dependent.sh脚本:

#!/bin/sh
# Dependent
touch         "one.txt" "two.txt"
archive_files "one.txt" "two.txt"

脚本是依赖的:它无法单独成功执行(未定义存档文件)。

可以在archive_files_ip.sh实现脚本中定义archive_files(在本例中使用zip):

#!/bin/sh
# Dependency
function archive_files {
    zip files.zip "$@"
}

您可以使用一个injector.sh“container”来包装这两个“components”,而不是直接在依赖脚本中源代码化实现脚本:

#!/bin/sh 
# Injector
source ./archive_files_zip.sh
source ./dependent.sh

archive_files依赖项刚刚注入到依赖脚本中。

您可能已经注入了使用tar或xz实现archive_files的依赖项。

示例:删除DI

如果dependent.sh脚本直接使用依赖项,则该方法将被称为依赖项查找(与依赖项注入相反):

#!/bin/sh
# Dependent

# dependency look-up
source ./archive_files_zip.sh

touch         "one.txt" "two.txt"
archive_files "one.txt" "two.txt"

现在的问题是依赖的“组件”必须自己执行初始化。

“组件”的源代码既不干净也不稳定,因为依赖项初始化中的每一次更改都需要“组件”源代码文件的新版本。

最后一句话

DI并不像Java框架那样被广泛强调和普及。

但这是一种通用的方法,可以解决以下问题:

应用程序开发(单一源代码发布生命周期)应用程序部署(具有独立生命周期的多个目标环境)

仅将配置与依赖项查找一起使用没有帮助,因为每个依赖项的配置参数数量(例如,新的身份验证类型)以及支持的依赖项类型数量(例如新的数据库类型)可能会发生变化。

我将提出一个稍微不同的、简短而精确的依赖注入定义,侧重于主要目标,而不是技术手段(从这里开始):

依赖注入是创建静态、无状态的服务对象图,其中每个服务由其依赖关系。

我们在应用程序中创建的对象(无论我们是否使用Java、C#或其他面向对象语言)通常分为两类:无状态、静态和全局“服务对象”(模块),以及有状态、动态和本地“数据对象”。

模块图(服务对象图)通常在应用程序启动时创建。这可以使用容器(如Spring)完成,但也可以通过向对象构造函数传递参数来手动完成。这两种方法都有其优点和缺点,但在应用程序中使用DI肯定不需要框架。

一个要求是服务必须通过其依赖性进行参数化。这意味着什么完全取决于给定系统中采用的语言和方法。通常,这采用构造函数参数的形式,但使用setter也是一种选择。这也意味着(在调用服务方法时)对服务的用户隐藏服务的依赖关系。

何时使用?我会说,每当应用程序足够大时,将逻辑封装到单独的模块中,在模块之间使用依赖关系图,可以提高代码的可读性和可探索性。

依赖注入(DI)是依赖反转原理(DIP)实践的一部分,也称为控制反转(IoC)。基本上,你需要做DIP,因为你想让你的代码更加模块化和单元可测试,而不是仅仅一个单片系统。因此,您开始识别可以从类中分离并抽象出来的代码部分。现在抽象的实现需要从类外部注入。通常这可以通过构造函数完成。因此,您创建了一个构造函数,它接受抽象作为参数,这称为依赖注入(通过构造函数)。有关DIP、DI和IoC容器的更多说明,请阅读此处

来自Book Apress.Spring.Persistence.with.HHibernate,2010年10月

依赖注入的目的是将解决应用程序业务中的外部软件组件逻辑。如果没有依赖注入访问所需的服务可能会与组件的密码这不仅增加了出错的可能性,还增加了代码膨胀,并放大了维护复杂性;它耦合组件更紧密地结合在一起,使得在重构或测试。

依赖注入是将依赖传递给其他对象或框架(依赖注入器)。

依赖注入使测试更容易。注入可以通过构造函数完成。

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年,当我写下这个答案时,这是谷歌测试博客的一个主要主题。这对我来说仍然是最大的优势,因为程序员在运行时设计中并不总是需要额外的灵活性(例如,服务定位器或类似模式)。程序员通常需要在测试期间隔离类。