依赖注入(Dependency Injection)

一、依赖注入的概念了解

 

介绍依赖注入(DI),首先要先了解一个概念——即控制反转(IoC)。

控制反转是面向对象编程的一种设计原则,可以用来减低计算机代码之间的耦合度。在传统的应用程序中,都是程序员手动在类的内部创建需要依赖的对象,而这种方式经常会导致类与类之间的高度耦合,难以测试。而当有了IoC容器之后,类把创建和查找依赖对象的权限都交给了容器,由容器进行注入组合对象,所以对象与对象之间是松散耦合,这一方便与测试,同时也利用功能的复用和代码结构的灵活。

DI(Dependency Injection),即“依赖注入”。IoC是一个很大的概念,可以用不同的方式实现。其主要表现形式主要有两种,一种是依赖查找,另外一种就是依赖注入了,依赖注入即组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。容器全权负责组件的装配,它会把符合依赖关系的对象通过JavaBean属性或者构造函数传递给需要的对象。通过JavaBean属性注射依赖关系的做法称为设置方法注入(Setter Injection);将依赖关系作为构造函数参数传入的做法称为构造器注入(Constructor Injection)。

二、依赖注入的优点、实现

在我们的实际生产中,任何一个应用都是由许多类组成,这些类相互之间按照协作来完成特定的业务逻辑。按照传统的方式,每个对象负责管理与自己相互协作的对象(即它所依赖的对象)的引用,这将会导致高度耦合和难以测试的代码。

举个例子,下面代码中的Lecturer类,只能执行讲授高等数学课的任务

public class Lecturer implements Teacher {
        
    private AdvancedMathematicsTask task;

    public Lecturer() {
        // 在这里与 MathLessonQeust紧耦合
        this.task = new AdvancedMathematicsTask();    
    }

    public void lectureLesson() {
        task.lecture();
    }

} 

 

可以从上面的代码中看出,lecturer在构造函数中创建了AdvancedMathematicsTask,这使得Lecturer和AdvancedMathematicsTask紧密地耦合到了一次,因此极大地限制了这个讲师的授课能力,如果这学期需要以为讲授高等数学的老师,那么这位讲师可以立刻就任,都是如果开一门线性代数,或者统计学,那么这位讲师就爱莫能助了。

更加糟糕的是,为这个Lecturer编写单元测试的时候将格外的困难。在这样的代码中,你必须保证当讲师的lectureLesson()方法被调用的时候,task的lecture()方法也要被调用。但是没有一个简单明了的方式能够实现这一点。很遗憾,Lecturer将无法进行测试。

而通过DI,对象的依赖关系将由系统中负责协调各对象的第三方组件来创建对象的时候进行设定。对象无需自行创建或管理他们的依赖关系。

下面我们通过代码,展示这一点

 public class Professor implements Teacher {
        
    private Task task;

    public Professor(Task task) {
        // Quest 被注入进来
        this.task = task;
    }
        
    public void lectureLesson() {
        task.lecture();
    }

}      

 

从上面代码,我们可以看到与之间不同,Professor没有自行创建授课任务,而是在构造器中把授课任务作为参数传入。这是依赖注入的方式之一,即前面提到的构造器注入。

更重要的是,传入的任务类型是Task,也就是所有授课任务都必须实现同一个接口。所以,Professor可以响应AdvancedMathematicsTask、LinearALgebraTask、StatisticsTask等任意Task的实现。

这里的要点是Professor没有与任何特定的Task实现发生耦合,对它来说,被要求讲授的课只要实现了Task接口,那么具体是哪门课就无所谓了。这就是DI所带来的最大收益——松耦合。如果一个对象只通过接口(而不是具体实现或初始化过程)来表明依赖关系,那么这种依赖就能够在对象本身毫不知情的情况下,用不同的具体实现进行替换。

对依赖进行替换的一个最采用方法就是在测试的时候使用mock实现。我们无法充分地测试Lecturer,因为它是紧耦合的;但是可以轻松地测试Professor,只需要给它一个Task的mock实现即可,如下面代码所示

import static org.mockito.Mockito.*;
import org.junit.Test;

public class ProfessorText {
    
    @Test
    public void ProfessorShouldLectureLesson() {
        Task mockTask = mock(Task.class); // 创建 mock Task
        Professor professor = new Professor(mockTask); // 注入 mock Task
        professor.lectureLesson();
        verify(mockTask, times(1)).lecture(); // 验证mockTask实现的lecture()方法仅被调用一次
    }
}

 
所以我们可以从上面的例子看出,DI能够让相互协作的软件组件保持松散耦合。

小结

DI是组装应用的一种方式,借助这种方式对象无需知道依赖来自何处或者依赖的实现方式。不同于自己获取依赖对象,对象会在运行期赋予它们所依赖的对象。依赖对象通常会通过接口了解所注入的对象,这样的话就能确保低耦合。

文章参考
《spring in action》

点赞