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

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


当前回答

这意味着对象应该只具有完成其工作所需的依赖项,并且依赖项应该很少。此外,如果可能的话,对象的依赖关系应该是接口,而不是“具体”对象。(具体对象是用关键字new创建的任何对象。)松散耦合促进了更高的可重用性,更容易维护,并允许您轻松地提供“模拟”对象来代替昂贵的服务。

“依赖注入”(DI)也称为“控制反转”(IoC),可以用作鼓励这种松散耦合的技术。

实施DI有两种主要方法:

构造函数注入设值注入

构造函数注入

这是一种将对象依赖关系传递给构造函数的技术。

注意,构造函数接受接口而不是具体对象。此外,请注意,如果orderDao参数为空,则会引发异常。这强调了接受有效依赖的重要性。在我看来,构造函数注入是赋予对象依赖关系的首选机制。在调用对象时,开发人员很清楚需要向“Person”对象提供哪些依赖关系才能正确执行。

沉淀剂注入

但是考虑下面的例子……假设您有一个类,它有十个没有依赖关系的方法,但是您要添加一个新方法,它确实依赖于IDAO。您可以将构造函数更改为使用构造函数注入,但这可能会迫使您更改所有的构造函数调用。或者,您可以添加一个新的构造函数来获取依赖项,但是开发人员如何轻松地知道何时使用一个构造函数而不是另一个构造函数。最后,如果依赖项的创建成本很高,为什么要创建它并传递给构造函数,因为它可能很少使用?“Setter Injection”是另一种DI技术,可用于此类情况。

Setter注入不会强制将依赖项传递给构造函数。相反,依赖项被设置到需要的对象公开的公共财产上。正如前面所暗示的,这样做的主要动机包括:

支持依赖注入而无需修改遗留类的构造函数。允许在需要时尽可能晚地创建昂贵的资源或服务。

下面是上述代码的示例:

public class Person {
    public Person() {}

    public IDAO Address {
        set { addressdao = value; }
        get {
            if (addressdao == null)
              throw new MemberAccessException("addressdao" +
                             " has not been initialized");
            return addressdao;
        }
    }

    public Address GetAddress() {
       // ... code that uses the addressdao object
       // to fetch address details from the datasource ...
    }

    // Should not be called directly;
    // use the public property instead
    private IDAO addressdao;

其他回答

依赖注入是基于框架构建的“控制反转”原则的一种实现。

GoF的“设计模式”中所述的框架是实现主控制流逻辑的类,从而使开发人员能够这样做,这样框架实现了控制原则的反转。

作为一种技术而不是作为类层次结构实现的方法,IoC原则只是依赖注入。

DI主要包括将类实例的映射和对这些实例的类型引用委托给外部“实体”:对象、静态类、组件、框架等。。。

类实例是“依赖项”,调用组件通过引用与类实例的外部绑定是“注入”。

显然,从OOP的角度来看,您可以以多种方式实现该技术,例如,构造函数注入、setter注入、接口注入。

授权第三方执行将引用与对象匹配的任务,这在您希望将需要某些服务的组件与同一服务实现完全分离时非常有用。

这样,在设计组件时,您可以只关注其体系结构和特定逻辑,信任与其他对象协作的接口,而不必担心所使用的对象/服务的任何类型的实现更改,如果您正在使用的同一对象将被完全替换(显然是尊重接口)。

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

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

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

简单来说,依赖注入(DI)是消除不同对象之间的依赖关系或紧密耦合的方法。依赖注入为每个对象提供一个内聚行为。

DI是国际奥委会春季原则的实施,该原则说“不要打电话给我们,我们会打电话给你”。使用依赖注入程序员不需要使用new关键字创建对象。

对象一旦加载到Spring容器中,我们就可以在需要时重用它们,方法是使用getBean(StringbeanName)方法从Spring容器中获取这些对象。

来自Christoffer Noring,Pablo Deeleman的书《学习角度-第二版》:

“随着我们的应用程序的增长和发展,我们的每一个代码实体都将在内部需要其他对象的实例,在软件工程领域中,这些对象被称为依赖关系。将这些依赖关系传递给依赖客户端的动作被称为注入,它还需要另一个代码主体(称为注入器)的参与。注入器将负责用于实例化和引导所需依赖项的功能,以便它们从成功注入客户端的那一刻起就可以使用。这一点非常重要,因为客户机不知道如何实例化自己的依赖关系,只知道为了使用它们而实现的接口。"

发件人:Anton Moiseev。《字体角度发展,第二版》一书:

“简而言之,DI帮助您以松散耦合的方式编写代码,并使代码更易于测试和重用。”

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

无依赖注入

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(); 
    ... 
  } 
}`