從GoF設計模式中,我們已經習慣一種思維編程方式:Interface Driven Design 介面驅動,介面驅動有很多好處,可以提供不同靈活的子類實現,增加代碼穩定和健壯性等等,但是介面一定是需要實現的,也就是如下語句遲早要執行:
AInterface a = new AInterfaceImp(); //AInterfaceImp是介面AInterface的一個子類
也就是說使用原廠模式,介面驅動編程並不能完全真正地實現松耦合,還是存在調用者與被調用者之間的依賴關係。比如下面這段代碼:一個Button控制Lamp的例子
public class Button {
private Lamp lamp;
public void push() {
lamp.turnOn();
}
}
但是馬上發現這個設計的問題,Button類直接依賴於Lamp類,這個依賴關係意味著當Lamp類修改時,Button類會受到影響。即Button控制Lamp,並且只能控制Lamp。顯然違反了“高層模組不應該依賴於低層模組,兩者都應該依賴於抽象;抽象不應該依賴於具體實現,細節應該依賴於抽象” 這一原則(DIP原則)。考慮到上述問題,自然的想到應該抽象出一個介面SwitchableDevice,來消除Button對Lamp的依賴,於是設計如下:
public class Button {
private SwitchableDevice lamp;
public Button(){
lamp= new Lamp();
}
public void push() {
lamp.turnOn();
}
}
但是由於被調用者名稱寫入了調用者的代碼中,這產生了一個介面實現的原罪:彼此聯絡,調用者和被調用者有緊密聯絡,在UML中是用依賴 Dependency 表示。
這種依賴在分離關注的思維下是不可忍耐的,必須切割,實現調用者和被調用者解耦,新的Ioc模式 Dependency Injection 模式由此產生了, Dependency Injection模式是依賴注射的意思,也就是將依賴先剝離,然後在適當時候再注射進入。
IoC(Inversion of Control)模式是一種用來解決組件(實際上也可以是簡單的Java類)之間依賴關係、配置及生命週期的設計模式,其中對組件依賴關係的處理是IoC的精華部分。IoC的實際意義就是把組件之間的依賴關係提取(反轉)出來,由容器來具體配置。這樣,各個組件之間就不存在hard-code的關聯,任何組件都可以最大程度的得到重用。運用了IoC模式後我們不再需要自己管理組件之間的依賴關係,只需要聲明由容器去實現這種依賴關係。就好像把對組件之間依賴關係的控制進行了倒置,不再由組件自己來建立這種依賴關係而交給容器去管理。
從上面可以看出IOC模式實際上也是延緩介面的實現,根據需要實現,因此,我們將人為控制介面的實現成為“注射”。
如果使用IOC模式來實現上述的代碼,可能類似下面這樣:
public class Button {
private SwitchableDevice lamp;
public Button(SwitchableDevice lamp){
this. lamp= lamp;
}
public void push() {
lamp.turnOn();
}
}
使用Ioc帶來的代價是:需要在用戶端或其它某處進行B和C之間聯絡的組裝。 所以,Ioc並沒有消除調用者和被調用者之間這樣的聯絡,只是轉移了這種聯絡。
好了,既然IOC能夠轉移依賴關係,實現組件之間的真正松耦合,但也存在一些缺點:
首先,使用IOC模式就必然會依賴於一些IOC容器,IOC的使用必然會降低系統效能(new 的速度肯定比Class.forName快);
其次,IOC容器的大量使用會造成額外的維護成本,很顯然,你現在不僅僅需要維護你的代碼,還需要維護你的applectionContext.xml,如果是WEB應用,則需要注意維護哪些Context-Param。尤其是,雖然代碼中不存在耦合關係,但是耦合關係都在設定檔中,你在寫出鬆散耦合的代碼的同時也必須去寫緊耦合的設定檔,對於一個大型系統而言,大量的設定檔的管理本身就必須付出高昂的代價。
再次,IOC模式的大量使用會降低一些複雜模組的可讀性,要知道,如果你不能寫出很好的文檔(多數人都是如此),那麼代碼就是你唯一可以與其他人溝通的語言。如果閱讀代碼的人不懂IOC,不會使用Spring呢,他如何理解你那些介面的實現?
因此,使用IOC模式要控制好使用的組件的粒度,比如,可以考慮在構件層級使用IOC模式,而在構件內部實現上使用GoF模式。