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

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


当前回答

使依赖注入概念易于理解。让我们以开关按钮为例来切换(打开/关闭)灯泡。

无依赖注入

Switch需要事先知道我连接到哪个灯泡(硬编码依赖项)。所以

开关->永久灯泡//开关直接连接到永久灯泡,测试不容易

Switch(){
PermanentBulb = new Bulb();
PermanentBulb.Toggle();
}

使用依赖注入

开关只知道我需要打开/关闭传递给我的灯泡。所以,

开关->灯泡1或灯泡2或夜灯泡(注入依赖性)

Switch(AnyBulb){ //pass it whichever bulb you like
AnyBulb.Toggle();
}

修改开关和灯泡的James示例:

public class SwitchTest { 
  TestToggleBulb() { 
    MockBulb mockbulb = new MockBulb(); 

    // MockBulb is a subclass of Bulb, so we can 
    // "inject" it here: 
    Switch switch = new Switch(mockBulb); 

    switch.ToggleBulb(); 
    mockBulb.AssertToggleWasCalled(); 
  } 
}

public class Switch { 
  private Bulb myBulb; 

  public Switch() { 
    myBulb = new Bulb(); 
  } 

  public Switch(Bulb useThisBulbInstead) { 
    myBulb = useThisBulbInstead; 
  } 

  public void ToggleBulb() { 
    ... 
    myBulb.Toggle(); 
    ... 
  } 
}`

其他回答

任何非平凡的应用程序都由两个或多个类组成,它们相互协作以执行某些业务逻辑。传统上,每个对象都负责获取自己对与其协作的对象(其依赖关系)的引用。在应用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年,当我写下这个答案时,这是谷歌测试博客的一个主要主题。这对我来说仍然是最大的优势,因为程序员在运行时设计中并不总是需要额外的灵活性(例如,服务定位器或类似模式)。程序员通常需要在测试期间隔离类。

使依赖注入概念易于理解。让我们以开关按钮为例来切换(打开/关闭)灯泡。

无依赖注入

Switch需要事先知道我连接到哪个灯泡(硬编码依赖项)。所以

开关->永久灯泡//开关直接连接到永久灯泡,测试不容易

Switch(){
PermanentBulb = new Bulb();
PermanentBulb.Toggle();
}

使用依赖注入

开关只知道我需要打开/关闭传递给我的灯泡。所以,

开关->灯泡1或灯泡2或夜灯泡(注入依赖性)

Switch(AnyBulb){ //pass it whichever bulb you like
AnyBulb.Toggle();
}

修改开关和灯泡的James示例:

public class SwitchTest { 
  TestToggleBulb() { 
    MockBulb mockbulb = new MockBulb(); 

    // MockBulb is a subclass of Bulb, so we can 
    // "inject" it here: 
    Switch switch = new Switch(mockBulb); 

    switch.ToggleBulb(); 
    mockBulb.AssertToggleWasCalled(); 
  } 
}

public class Switch { 
  private Bulb myBulb; 

  public Switch() { 
    myBulb = new Bulb(); 
  } 

  public Switch(Bulb useThisBulbInstead) { 
    myBulb = useThisBulbInstead; 
  } 

  public void ToggleBulb() { 
    ... 
    myBulb.Toggle(); 
    ... 
  } 
}`

依赖注入是解决“依赖混淆”需求的一种可能方案。依赖性混淆是一种将“明显”性质从向需要依赖性的类提供依赖性的过程中去除的方法,因此在某种程度上混淆了向所述类提供所述依赖性。这不一定是坏事。事实上,通过混淆向类提供依赖项的方式,类外部的某个东西负责创建依赖项,这意味着在各种情况下,可以向类提供不同的依赖项实现,而不需要对类进行任何更改。这对于在生产和测试模式之间切换非常有用(例如,使用“模拟”服务依赖)。

不幸的是,糟糕的部分是,有些人认为你需要一个专门的框架来进行依赖性混淆,如果你选择不使用特定的框架来做,那么你在某种程度上就是一个“低级”程序员。另一个非常令人不安的神话是,依赖性注入是实现依赖性混淆的唯一方法。这显然是历史性的,显然是100%错误的,但你很难说服一些人,依赖项注入可以替代依赖项混淆需求。

多年来,程序员们已经了解了依赖性混淆的需求,在考虑依赖性注入之前和之后,许多替代解决方案都已经发展起来。有工厂模式,但也有许多使用ThreadLocal的选项,其中不需要对特定实例进行注入-依赖关系被有效地注入到线程中,这样做的好处是使对象(通过方便的静态getter方法)可用于任何需要它的类,而无需向需要它的类别添加注释并设置复杂的XML“粘合”以实现这一点。当持久性需要依赖项(JPA/JDO或其他)时,它允许您更容易地实现“跨持久性”,并且域模型和业务模型类完全由POJO组成(即没有特定于框架的/锁定在注释中的)。

“依赖注入”不就是指使用参数化构造函数和公共setter吗?

James Shore的文章展示了以下示例进行比较。

没有依赖注入的构造函数:公共类示例{私有数据库Thingie myDatabase;public Example(){myDatabase=新数据库Thingie();} public void doStuff(){... myDatabase.getData();... } } 具有依赖注入的构造函数:公共类示例{私有数据库Thingie myDatabase;公共示例(DatabaseThingie使用ThisDatabaseReplace){myDatabase=改用此数据库;}public void doStuff(){... myDatabase.getData();... } }