八一八 依賴注入

0. 前言

在軟件工程領域,依賴注入(Dependency Injection)是用於實現控制反轉(Inversion of Control)的最多見的方式之一。本文主要介紹依賴注入原理和常見的實現方式,重點在於介紹這種年輕的設計模式的適用場景及優點。mysql

1. 爲何須要依賴注入

控制反轉用於解耦,解的到底是誰和誰的耦?這是我在最初瞭解依賴注入時候產生的第一個問題。sql

下面我引用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 {
    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的實現了。this

1
2
3
4
5
6
7
8
public class MovieLister {
    private MovieFinder finder;

    public MovieLister(MovieFinder finder) {
        this.finder = finder;
    }
    ...
}

 

2.2 setter注入

相似的,咱們能夠增長一個setter函數來傳入建立好的MovieFinder對象,這樣一樣能夠避免在MovieFinder中hard init這個對象。編碼

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

 

2.3 接口注入

接口注入使用接口來提供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的實現。

 

3. 最後

依賴注入下降了依賴和被依賴類型間的耦合,在修改被依賴的類型實現時,不須要修改依賴類型的實現,同時,對於依賴類型的測試,能夠更方便的使用mocking object替代原有的被依賴類型,以達到對依賴對象獨立進行單元測試的目的。

最後須要注意的是,依賴注入只是控制反轉的一種實現方式。控制反轉還有一種常見的實現方式稱爲依賴查找。

相關文章
相關標籤/搜索