什麼是控制反轉/依賴注入?

控制反轉(IoC=Inversion of Control)IoC,用白話來講,就是由容器控制程序之間的(依賴)關係,而非傳統實現中,由程序代碼直接操控。這也就是所謂“控制反轉”的概念所在:(依賴)控制權由應用代碼中轉到了外部容器,控制權的轉移,是所謂反轉。


    IoC也稱爲好萊塢原則(Hollywood Principle):“Don’t call us, we’ll call you”。即,如果大腕明星想演節目,不用自己去找好萊塢公司,而是由好萊塢公司主動去找他們(當然,之前這些明星必須要在好萊塢登記過)。

正在業界爲IoC爭吵不休時,大師級人物Martin Fowler也站出來發話,以一篇經典文章《Inversion of Control Containers and the Dependency Injection pattern》爲IoC正名,至此,IoC又獲得了一個新的名字:“依賴注入 (Dependency Injection)”。



    相對IoC 而言,“依賴注入”的確更加準確的描述了這種古老而又時興的設計理念。從名字上理解,所謂依賴注入,即組件之間的依賴關係由容器在運行期決定,形象的來說,即由容器動態的將某種依賴關係注入到組件之中。



    例如前面用戶註冊的例子。UserRegister依賴於UserDao的實現類,在最後的改進中我們使用IoC容器在運行期動態的爲UserRegister注入UserDao的實現類。即UserRegister對UserDao的依賴關係由容器注入,UserRegister不用關心UserDao的任何具體實現類。如果要更改用戶的持久化方式,只要修改配置文件applicationContext.xm即可。



   
     依賴注入機制減輕了組件之間的依賴關係,同時也大大提高了組件的可移植性,這意味着,組件得到重用的機會將會更多。


依賴注入的三種實現形式

我們將組件的依賴關係由容器實現,那麼容器如何知道一個組件依賴哪些其它的組件呢?例如用戶註冊的例子:容器如何得知UserRegister依賴於UserDao呢。這樣,我們的組件必須提供一系列所謂的回調方法(這個方法並不是具體的Java類的方法),這些回調方法會告知容器它所依賴的組件。根據回調方法的不同,我們可以將IoC分爲三種形式:



Type1-接口注入(Interface Injection)


它是在一個接口中定義需要注入的信息,並通過接口完成注入。Apache Avalon是一個較爲典型的Type1型IOC容器,WebWork框架的IoC容器也是Type1型。

當然,使用接口注入我們首先要定義一個接口,組件的注入將通過這個接口進行。我們還是以用戶註冊爲例,我們開發一個InjectUserDao接口,它的用途是將一個UserDao實例注入到實現該接口的類中。InjectUserDao接口代碼如下:


public interface InjectUserDao {

    
public void
 setUserDao(UserDao userDao);

}



UserRegister需要容器爲它注入一個UserDao的實例,則它必須實現InjectUserDao接口。UserRegister部分代碼如下:


public class UserRegister implements InjectUserDao{

    
private UserDao userDao = null;//該對象實例由容器注入


    
public void setUserDao(UserDao userDao) {

        
this.userDao =
 userDao;

    }


// UserRegister的其它業務方法

}



同時,我們需要配置InjectUserDao接口和UserDao的實現類。如果使用WebWork框架則配置文件如下:


<component>

        
<scope>request</scope>

        
<class>com.dev.spring.simple.MemoryUserDao</class>

        
<enabler>com.dev.spring.simple.InjectUserDao</enabler>

</component>



這樣,當IoC容器判斷出UserRegister組件實現了InjectUserDao接口時,它就將MemoryUserDao實例注入到UserRegister組件中。


Type2-設值方法注入(Setter Injection)


在各種類型的依賴注入模式中,設值注入模式在實際開發中得到了最廣泛的應用(其中很大一部分得力於Spring框架的影響)。

基於設置模式的依賴注入機制更加直觀、也更加自然。前面的用戶註冊示例,就是典

型的設置注入,即通過類的setter方法完成依賴關係的設置。


Type3-構造子注入(Constructor Injection)

構造子注入,即通過構造函數完成依賴關係的設定。將用戶註冊示例該爲構造子注入,UserRegister代碼如下:


public class UserRegister {

    
private UserDao userDao = null;//由容器通過構造函數注入的實例對象


    
public UserRegister(UserDao userDao){

        
this.userDao =
 userDao;

    }


    
//業務方法

}


幾種依賴注入模式的對比總結

接口注入模式因爲歷史較爲悠久,在很多容器中都已經得到應用。但由於其在靈活性、易用性上不如

其他兩種注入模式,因而在IOC的專題世界內並不被看好。

Type2和Type3型的依賴注入實現則是目前主流的IOC實現模式。這兩種實現方式各有特點,也各具優勢。


Type2 設值注入的優勢

1. 對於習慣了傳統JavaBean開發的程序員而言,通過setter方法設定依賴關係顯得更加直觀,更加自然。

2. 如果依賴關係(或繼承關係)較爲複雜,那麼Type3模式的構造函數也會相當龐大(我們需要在構造函數中設定所有依賴關係),此時Type2模式往往更爲簡潔。

3. 對於某些第三方類庫而言,可能要求我們的組件必須提供一個默認的構造函數(如Struts中的Action),此時Type3類型的依賴注入機制就體現出其侷限性,難以完成我們期望的功能。



Type3 構造子注入的優勢:

1. “在構造期即創建一個完整、合法的對象”,對於這條Java設計原則,Type3無疑是最好的響應者。

2. 避免了繁瑣的setter方法的編寫,所有依賴關係均在構造函數中設定,依賴關係集中呈現,更加易讀。

3. 由於沒有setter方法,依賴關係在構造時由容器一次性設定,因此組件在被創建之後即處於相對“不變”的穩定狀態,無需擔心上層代碼在調用過程中執行setter方法對組件依賴關係產生破壞,特別是對於Singleton模式的組件而言,這可能對整個系統產生重大的影響。

4. 同樣,由於關聯關係僅在構造函數中表達,只有組件創建者需要關心組件內部的依賴關係。對調用者而言,組件中的依賴關係處於黑盒之中。對上層屏蔽不必要的信息,也爲系統的層次清晰性提供了保證。

5. 通過構造子注入,意味着我們可以在構造函數中決定依賴關係的注入順序,對於一個大量依賴外部服務的組件而言,依賴關係的獲得順序可能非常重要,比如某個依賴關係注入的先決條件是組件的UserDao及相關資源已經被設定。

可見,Type3和Type2模式各有千秋,而Spring、PicoContainer都對Type3和Type2類型的依賴注入機制提供了良好支持。這也就爲我們提供了更多的選擇餘地。理論上,以Type3類型爲主,輔之以Type2類型機制作爲補充,可以達到最好的依賴注入效果,不過對於基於Spring Framework開發的應用而言,Type2使用更加廣泛。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章