# 依赖注入原理

# 0. 前言
在软件工程领域，依赖注入（Dependency Injection）是用于实现控制反转（Inversion of Control）的最常见的方式之一。本文主要介绍依赖注入原理和常见的实现方式，重点在于介绍这种年轻的设计模式的适用场景及优势。
# 1. 为什么需要依赖注入
控制反转用于解耦，解的究竟是谁和谁的耦？这是我在最初了解依赖注入时候产生的第一个问题。
下面我引用Martin Flower在解释介绍注入时使用的一部分代码来说明这个问题。
```
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()]);
    }
    ...
}
```
```
public interface MovieFinder {
    List findAll();
}
```
我们创建了一个名为MovieLister的类来提供需要的电影列表，它moviesDirectedBy方法提供根据导演名来搜索电影的方式。真正负责搜索电影的是实现了MovieFinder接口的MovieFinderImpl，我们的MovieLister类在构造函数中创建了一个MovieFinderImpl的对象。
目前看来，一切都不错。但是，当我们希望修改finder，将finder替换为一种新的实现时（比如为MovieFinder增加一个参数表明Movie数据的来源是哪个数据库），我们不仅需要修改MovieFinderImpl类，还需要修改我们MovieLister中创建MovieFinderImpl的代码。
** 这就是依赖注入要处理的耦合。这种在MovieLister中创建MovieFinderImpl的方式，使得MovieLister不仅仅依赖于MovieFinder这个接口，它还依赖于MovieListImpl这个实现。** 这种在一个类中直接创建另一个类的对象的代码，和硬编码（hard-coded strings)以及硬编码的数字（magic numbers）一样，是一种导致耦合的坏味道，我们可以把这种坏味道称为硬初始化（hard init）。同时，我们也应该像记住硬编码一样记住，new（对象创建）是有毒的。
Hard Init带来的主要坏处有两个方面：1）上文所述的修改其实现时，需要修改创建处的代码；2）不便于测试，这种方式创建的类（上文中的MovieLister）无法单独被测试，其行为和MovieFinderImpl紧紧耦合在一起，同时，也会导致代码的可读性问题（“如果一段代码不便于测试，那么它一定不便于阅读。”）。
# 2. 依赖注入的实现方式
依赖注入其实并不神奇，我们日常的代码中很多都用到了依赖注入，但很少注意到它，也很少主动使用依赖注入进行解耦。这里我们简单介绍一下赖注入实现三种的方式。
## 2.1 构造函数注入（Contructor Injection）
这是我认为的最简单的依赖注入方式，我们修改一下上面代码中MovieList的构造函数，使得MovieFinderImpl的实现在MovieLister类之外创建。这样，MovieLister就只依赖于我们定义的MovieFinder接口，而不依赖于MovieFinder的实现了。
```
public class MovieLister {
    private MovieFinder finder;

    public MovieLister(MovieFinder finder) {
        this.finder = finder;
    }
    ...
}
```
## 2.2 setter注入
类似的，我们可以增加一个setter函数来传入创建好的MovieFinder对象，这样同样可以避免在MovieFinder中hard init这个对象。 
```
public class MovieLister {
    s...
    public void setFinder(MovieFinder finder) {
        this.finder = finder;
    }
}
```
## 2.3 接口注入
接口注入使用接口来提供setter方法，其实现方式如下。
首先要创建一个注入使用的接口。
```
public interface InjectFinder {
    void injectFinder(MovieFinder finder);
}
```
之后，我们让MovieLister实现这个接口。
```
class MovieLister implements InjectFinder {
    ...
    public void injectFinder(MovieFinder finder) {
      this.finder = finder;
    }
    ...
}
```
最后，我们需要根据不同的框架创建被依赖的MovieFinder的实现。
# 3. 最后
依赖注入降低了依赖和被依赖类型间的耦合，在修改被依赖的类型实现时，不需要修改依赖类型的实现，同时，对于依赖类型的测试，可以更方便的使用mocking object替代原有的被依赖类型，以达到对依赖对象独立进行单元测试的目的。
最后需要注意的是，依赖注入只是控制反转的一种实现方式。控制反转还有一种常见的实现方式称为依赖查找。
# 参考
1. [Inversion of Control Containers and the Dependency Injection pattern](http://martinfowler.com/articles/injection.html)
2. [How to Think About the "new" Operator with Respect to Unit Testing](http://googletesting.blogspot.jp/2008/07/how-to-think-about-new-operator-with.html)
3. [依赖注入](https://github.com/android-cn/blog/tree/master/java/dependency-injection)
