Leo's Blog

依赖注入

前言

先来一段看似比较装逼的介绍。在软件工程领域,依赖注入(Dependency Injection)是用于实现控制反转(Inversion of Control)的最常见的方式之一。本文主要介绍依赖注入原理和常见的实现方式

依赖注入的作用

控制反转用于解耦,解的究竟是什么?
引用一下 Martin Flower在介绍注入时使用的部分代码说明这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class MovieLister{
private MovieFinder finder;
public MovieLister(){
finder = new MovieFinderImpl();
}
public Movie[] moviesDirectedBy(String arg){
List allMovies = finder.findAll();
for(Iterator it = allMovies.iterator(); it.hasNext();){
Movie movie = (Movie)it.next();
if(!movie.getDirector().equals(arg)) it.remove();
}
return (Movie[]) allMovies.toArray(new Movie[allMovies.size()]);
}
...
}

1
2
3
public interface MovieFinder{
void findAll();
}

脑补一下这段代码的功能:

  • MovieLister的类来提供需要的电影列表,依赖于MovieFinder对象
  • movieDirectedBy方法根据导演名来筛选电影
  • MovieFinder接口的实现类MovieFinderImpl负责与数据库交互,搜索电影

目前看来,我们完美的实现了功能!然而,我们都晓得,需求是无时无刻不在改变的~😭,现在我们需要将finder的实现改变(比如增加一个参数)。那我们就需要修改多个类。

这就是依赖注入需要处理的耦合。这种在MovieLister中创建MovieFinderImpl的方式,是的MovieLister不仅仅依赖MovieFinder接口,还依赖于MovieFinderImpl这个实现。这种在一个类中,直接创建另一个累的对象的代码,我们称之为hard init , 它是有毒的:

  • 修改实现时,我们需要修改new Object的代码
  • 不便于测试,上文中的MovieLister无法单独被测试,其行为和MovieFinderImpl紧紧耦合在一起。

依赖注入的实现方式

其实我们在平常的工作过程中,会经常使用依赖注入,只不过很少注意(反正我是。。。),也不太注意使用依赖注入进行解耦。我们在这里介绍一下依赖注入实现的三种方式。

构造函数注入(Contructor Injection)

在类的外面创建对象,然后通过构造方法传入。

1
2
3
4
5
6
7
8
// 构造函数注入,MovieLister类就只依赖于我们定义的MovieFinder接口
public class MovieLister{
private MovieFinder finder;
public MovieLister(MovieFinder finder){
this.finder = finder;
}
}

setter方法注入

增加一个setter方法来传入创建好的MovieFinder对象,同样可以避免在MovieFinder中hard init这个对象

1
2
3
4
5
6
public class MovieLister{
...
public void setFinder(MovieFinder finder){
this.finder = finder;
}
}

接口注入

接口注入使用接口来提供setter方法,其实现方法如下
首先创建一个注入使用的接口

1
2
3
public interface InjectFinder{
void injectFinder(MovieFinder finder)
}

之后我们让MovieLister实现这个接口

1
2
3
4
5
6
7
class MovieLister implements InjectFinder{
...
public void injectFinder(MovieFinder finder){
this.finder = finder;
}
...
}

最后我们需要根据不同的框架创建被依赖的MovieFinder的实现。

Java中的注解依赖注入

在java中,使用注解进行依赖注入是最常用的。通过在字段的声明前添加@Inject注解进行标记,来实现依赖对象的自动注入。

1
2
3
4
5
6
7
8
public class Human{
...
@Inject Father father
...
public Human(){
}
}

神奇的@Inject注解,一个注解就自动注入了? 实质上,我们还需要使用依赖注入框架,进行一些配置。比如Dagger。

参考:

  1. Inversion of Control Containers and the Dependency Injection pattern
  2. 依赖注入