依賴注入(Dependency Injection)模式的特點分析與實現
――構造子注入(Constructor Injection)模式的分析與實現
蔡 超
(chaocai2001@yahoo.com.cn)
摘要:本文對IoC模式、依賴注入(Dependency Injection) 模式做了簡要介紹,文中分析構造子注入模式與其他模式相比較的優勢和特點,并給出了在JAVA中實現該模式的方法。
1 引言
IoC(Inversion of Control)模式以被目前的輕量級容器所廣泛應用,通過IoC模式這些容器幫助開發者將來自不同項目的組件裝配成一個內聚的應用程序。輕量級的IoC容器(如Spring、pico-container)雖然為我們的開發提供了很大的便利,但是在很多情況下這些輕量級容器所提供的功能并不一定非常適合我們的需要,也許這些容器的功能過于龐大了,或者所提供的功能缺乏對特定應用的針對性,或者我們需要更高的運行效率,這時我們可以在了解IoC的原理的基礎上利用JAVA的反射機制自己實現靈活的、可擴展的組件機制。
2 IoC與依賴注入(Dependency Injection)模式簡介
與GoF的設計模式相同,IoC模式同樣是關注重用性,但與GoF模式不同的是IoC模式更加關注二進制級的重用性和可擴展性,即可以直接通過二進制級進行擴充,復用的模塊通常被稱為組件或者插件,組件和插件都是在運行時進行裝載的。
GoF的設計模式中我們大量看到的是面向接口編程:Interface Driven Design 接口驅動,接口驅動有很多好處,可以提供不同靈活的子類實現,增加代碼穩定和健壯性等等,但是接口一定是需要實現的,也就是如下語句遲早要執行:
AInterface a = new AInterfaceImp();
由于以上的代碼被寫入了調用者程序中,同時象AinterfaceImp這樣的接口的實現類是在編譯時被裝載的,如果以后想加入新的接口實現類則必須修改調用者的代碼。
IoC模式與以上情況不同,接口的實現類是在運行時被裝載的,這樣即使以后新添加了接口實現類是也不需修改調用者的代碼(可以通過特定的方式來定位新增的實現類,如配置文件指定)。IoC英文為 Inversion of Control,即反轉模式,這里有著名的好萊塢理論:你呆著別動,到時我會找你。
IoC模式可以延緩接口的實現,根據需要實現,有個比喻:接口如同空的模型套,在必要時,需要向模型套注射石膏,這樣才能成為一個模型實體,因此,對于這些新生的容器,它們反轉的是“如何定位插件的具體實現”。因此, Martin Fowler 給這種模式起了一個形象的名稱“依賴注入”(Dependency Injection)。
圖表 1 采用Dependency Injection前后的依賴關系變化
依賴注入的形式主要有三種,分別將它們叫做構造子注入(Constructor Injection)、設值方法注入(Setter Injection)和接口注入(Interface Injection)。
這三種方式在Martin Fowler的《Inversion of Control Containers and the Dependency Injection pattern》中都給出了詳細的定義及說明,本文就不再贅述了,下面的內容將著重介紹構造子注入模式的特點及實現方法。
3 構造子注入模式的特點及實現
3.1 構造子注入模式的特點
通常情況下設值方法注入和接口注入較易于被開發人員接受,而構造子注入則應用較少,實際上構造子注入具有很多其他兩者所不具有的優勢:
1 構造子注入形成了一種更強的依賴契約
2 可以獲得更加簡明的代碼
3 更加簡明的依賴聲明機制,無須定義XML配置文件或設值方法
4 更加符合接口與實現分離的組件特征,組件接口表明能夠向其它組件提供的服務,而實現則應該是所提供服務的實現應該與服務契約無關(即不應包含用于獲得依賴的設值方法等)。
5 不會出現不確定的狀態。在設值方法注入中,由于并不是所有的設值方法(setter)都一定會被調用的,所以會有不確定狀態。
從以上幾點我們還可以分析出構造子注入對于組件代碼的入侵性遠小于其它兩種模式(接口注入使得組件必須實現特定接口,設值方法同樣要求組件提供特定的setter方法),代碼更加易于維護。
圖表 2 示例中類的關系
Client的實現依賴于接口A、B和C的實現,但是為了提供系統更好的靈活性和可擴展性,各接口的實現以組件的方式利用java的反射機制進行運行時裝載,注意到組件間可能會存在某種依賴關系,例如組件AX依賴與接口B的實現類,而這中依賴關系必須在運行時動態注入,組件為了告訴組件的調用者這種依賴關系以便注入,可以使用上文提到的各種模式:
1 使用接口注入模式
public interface InjectB{
public void injectB(B bImp);
}
public interface InjectC{
public void injectC(C cImp);
}
public class AImp implements A,InjectB,InjectC{
…
public void injectB(B bImp);
public void injectC(C cImp);
…
}
2 使用設值注入模式
public class AImp implements A {
…
public void setB(B bImp);
public void setC(C cImp);
…
}
3 使用構造子注入模式
public class AImp implements A {
…
public AImp(B bImp, C cImp){
…
}
…
}
由以上實例可以清楚的看出采用構造子注入模式的實現組件代碼最為簡單,且所受的入侵性最小。
3.2 在JAVA中實現構造子注入模式
在java及.NET這樣具有反射功能的語言中實現類型的運行時載入并不復雜,只要通過Class.forName或生成自己的ClassLoader就可以實現。
同樣我們可以通過反射機制獲取組件構造函數的參數,注入相應接口的實現,作者將此過程進行了封裝,以下是代碼:
public class RefectHelper {
public Object ConstructorHelper(String className,ConstructorParamDeal pd) throws Exception{
try{
//獲取類中的構造函數
Constructor[] constructs=Class.forName(className).getConstructors(); //實現中默認使用第一個構造函數類創建實例
Class [] classes=constructs[0].getParameterTypes();
//獲取要注入的參數實例
Object []obj=pd.dealParam(classes);
//創建實例
return constructs[0].newInstance(obj);
}catch(Exception e){
throw e;
}
}
}
/**
*構造函數參數注入
**/
public interface ConstructorParamDeal {
/**
*根據構造函數中參數的類型注入,相應的實現
@param classes 構造函數的參數類型
◎return 注入構造函數的參數實現
**/
public Object [] dealParam(Class [] classes);
}
public class ParamDeal implements ConstructorParamDeal{
/* (non-Javadoc)
* @see com.topsec.tsm.agent.helper.ConstructorParamDeal#dealParam(java.lang.Class[])
*/
public Object [] dealParam(Class[] classes) {
Object [] obj=new Object[classes.length];
for (int i=0;i<obj.length;i++){
//為不同類型注入選擇不同實例
if (classes[i].equals(String.class)){
obj[i]=”Hello World”;
}
}
return obj;
}
}
上面的程序中ConstructorHelper用于利用反射機制枚舉出載入類的構造函數及構造函數的參數的類型,至于不同類型注入什么樣的實例則由ContructorParamDeal的實現者來決定,ContructorParamDeal的實現者同樣可以以組件的形式在運行時動態載入。由于組件間的依賴關系的制約,所以組件實例化的順序需要特別考慮。
4 結束語
三種依賴注入模式各有其特點和優勢,只有充分理解這些模式間的不同,才能為自己的應用選擇正確的依賴注入模式,文中介紹的構造子注入模式實現方法,在使用其他具有反射功能的語言(如:.NET)時同樣可以參考。
[參考文獻]
1 Martin Fowler,Inversion of Control Containers and the Dependency Injection pattern,http://www.martinfowler.com/articles/injection.html,2004
2 Erich Gamma,Design Patterns,Addison Wesley,1999
3 http://www.picocontainer.org/
4 彭晨陽,Ioc模式, http://www.jdon.com,2004