原文叫看《墨攻》理解IOC概念
2006年多部賀歲大片以讓人應接不暇的頻率紛至沓來,其中張之亮的《墨攻》算是比較出彩的一部,講述了戰國時期墨家人革離幫助梁
國反抗趙國侵略的個人英雄主義故事,恢宏壯闊,渾雄凝重的歷史場面相當震撼。其中有一個場景:當劉德華所飾的墨者革離到達梁國都城
下,城上梁國守軍問:“來者何人?”,劉德華回答:“墨者革離!”,我們不妨用C#(原文是java,我修改)對這段“城門問對”的場景進行編劇並借由這個例子來理解IoC的內涵。
劇本和飾演者耦合
MoAttack代表《墨攻》的劇本,cityGetAsk()代表“城門問對”這段劇情,LiuDeHua是具體飾演者劉德華:
代碼清單1
public class MoAttack {
public MoAttack() {}
public void cityGateAsk(){
LiuDeHua ldh = new LiuDeHua(); ① 演員直接侵入劇本
ldh.responseAsk("墨者革離!");
}
}
我們會發現以上劇本在①處,作為具體飾演者的劉德華直接侵入到劇本中,使劇本和演員直接耦合在一起:
圖(1)劇本與演員直接耦合
一個明智的編劇在劇情創作時應圍繞故事的角色進行,而不應考慮角色的具體飾演者,這樣才可能在劇本投拍時自由地選擇任何適合的演員,而非綁定在劉德華一人身上。通過以上的分析,我們知道需要為該劇本主人公革離定義一個接口,以角色進行劇情安排,飾演者實現角色的接口:
代碼清單2 MoAttack:引入劇本角色
public class MoAttack{
public MoAttack() {}
public void cityGateAsk()
{
GeLi geli = new LiuDeHua(); ① 引入革離角色接口
geli.responseAsk("墨者革離!"); ② 通過接口開展劇情
}
}
在①處引入了劇本的角色——革離,劇本的情節通過角色展開,在拍攝時角色的事跡由演員表現,如②處所示。因此劇本、革離、劉德華三者的類圖關系如圖2所示:
圖2劇本、革離、劉德華三者的類圖關系
我們希望劇本和演員無關,可是,在圖2中,我們看到MoAttack同時依賴於GeLi接口和LiuDeHua類,並沒有達到我們所期望的劇本僅依賴於角色的目的。可是角色最終又必須通過具體的演員才能完成拍攝,如何將讓LiuDeHua和劇本無關而又能完成GeLi的具體動作呢?當然是在影片投拍時,導演將LiuDeHua安排在GeLi的角色上,通過導演之手將劇本、角色、飾演者裝配起來。
圖3劇本和飾演者解耦了
通過引入導演,劇本和具體的飾演者解耦了,對應到軟件中,導演象是一個裝配器,將具體的飾演者賦給了劇本的角色。
現在我們可以反過來講解IOC的概念了。IOC(Inverse of Control)的字面意思是控制反轉,它包括兩個層面的內容:其一是“控制”,其二是“反轉”,到底是什么東西的控制被反轉了呢?對應到前面的例子, “控制”是指GeLi角色扮演者的選擇控制權,“反轉”是指這種選擇控制權從《墨攻》劇本中移除,轉交到導演的手中。對於程序來說,即是某一接口具體實現類的選擇控制權從客戶類中移除,轉交給第三方來確定,客戶類不知道是哪個具體的實現類,它通過接口方法對實現類進行調用。
因為IOC確實不夠開門見山,因此業界曾進行了廣泛的討論,最終軟件界的泰斗級人物Martin Fowler提出了DI(依賴注入:Dependency Injection)的概念,即將客戶類對接口實現類的依賴關系由第三方(容器或協作類)注入,以移除客戶類對具體接口實現類的依賴。“依賴注入”的概念顯然比“控制反轉”直接達意,易於理解。
IOC的三種類型
從注入方法上看,主要可以划分為三種的注入類型,分別是構造函數注入、屬性注入和接口注入,Spring.Net支持構造函數注入和屬性注入。下面我們繼續使用以上的例子說明這三種注入方法的區別。
構造函數注入
我們通過客戶類的構造函數,將接口實現類通過接口變量傳入,如代碼清單3所示:
代碼清單3 MoAttack:通過構造函數注入革離扮演者
public class MoAttack{
public MoAttack(){}
private GeLi geli;
public MoAttack(GeLi geli){ ① 注入革離的具體扮演者
this.geli = geli;
}
public void cityGateAsk()
{
geli.responseAsk(“墨者革離!”);
}
}
MoAttack的構造函數不關心具體是誰扮演革離這個角色,只要在①處傳入的扮演者按劇本要求完成角色功能即可。
角色的具體扮演者由導演來安排,如代碼清單4所示:
代碼清單 4 Director:通過構造函數注入革離扮演者
public class Director {
public void direct(){
GeLi geli = new LiuDeHua(); ① 指定角色的扮演者
MoAttack moAttack = new MoAttack(geli); ② 注入具體扮演者到劇本中
moAttack.cityGateAsk();
}
}
屬性注入
有時,導演會發現,雖然革離是影片《墨攻》的第一主人公,但並非每場戲都需要革離的出現,通過構造函數方式注入顯得很不妥當,在這種情況下,可以使用屬性注入進行改造。屬性注入通過通.Net 屬性完成客戶類所需依賴的注入,更靈活,更方便。
代碼清單5 MoAttack:通.Net 屬性器注入革離扮演者
public class MoAttack{
private GeLi gelii;
public GeLi Gelii{ ① 屬性注入方法
set{ gelii = value; }
}
public void cityGateAsk() ...{
geli.responseAsk("墨者革離");
}
}
MoAttack在①處為geli 字段提供一個屬性,以便讓導演在拍需要革離的戲時才將注入geli的具體扮演者,而不需要劉德華從頭到尾跟着墨攻劇組跑。
代碼清單 6 Director:通過屬性注入革離扮演者
public class Director{
public void direct(){
GeLi geli = new LiuDeHua();
MoAttack moAttack = new MoAttack();
moAttack.Gelii = geli; ① 調用屬性注入
moAttack.cityGateAsk();
}
}
和通過構造函數注入革離扮演者不同,在實例化MoAttack時,並未指定任何扮演者,而是在實例化MoAttack后,調用其屬性注入扮演者。按照類似的方式,我們還可以為劇本中其他如巷淹中,梁王等角色分別提供注入的屬性,導演即可以根據所拍劇段的不同,注入所需要的角色了。
接口注入
將客戶類所有注入的方法抽取到一個接口中,客戶類通過實現這一接口提供注入的方法。為了采取接口注入的方式,需要聲明一個額外的接口:
public interface IActorArrangable{
void injectGeli(GeLi geli);
}
然后,MoAttack實現這個接口並實現接口中的方法:
代碼清單7 MoAttack:通過接口方法注入革離扮演者
public class MoAttack : IActorArrangable{
private GeLi geli;
public void injectGeli (GeLi geli) { ① 實現接口方法
this.geli = geli;
}
public void cityGateAsk() ...{
geli.responseAsk("墨者革離");
}
}
Director通過IActorArrangable接口的injectGeli()方法完成扮演者的注入工作。
代碼清單 8 Director:通過接口方法注入革離扮演者
public class Director{
public void direct(){
GeLi geli = new LiuDeHua();
MoAttack moAttack = new MoAttack();
moAttack.injectGeli (geli);
moAttack.cityGateAsk();
}
}
由於通過接口注入需要額外聲明一個接口,增加了類的數目,而且它的效果和屬性注入並無本質區別,因此我們不提倡這種方式。
通過容器完成依賴關系的建立
雖然MoAttack和LiuDeHua實現了解耦,無需關注實現類的實例化工作,但這些工作在代碼中依然存在,只是轉移到Director中而已,導致導演的權力非常大,潛規則不斷滋生。假設某一制片人想改變這一局面,在相中某個劇本后,通過一個“海選”或者第三公正中介來選擇導演、演員,讓他們各司其職,那劇本、導演、演員就都實現解耦了。
所謂媒體“海選”和中介機構在程序領域即是一個第三方容器,它幫助我們完成類的初始化和裝配工作,讓我們從這些底層的實現類實例化,依賴關系的裝配中脫離出來,專注於更有意思的業務代碼的編寫工作,那確實是挺愜意的事情。Spring.Net就是這樣一個容器,它通過配置文件描述類之間的依賴關系,下面是Spring.Net配置文件的對以上實例進行配置的樣式代碼:
<objects>
<object id="geli" type="com.baobaotao.LiuDeHua"></object>
<object id="moAttack" type=" com.baobaotao.MoAttack">
<property name="geli"><ref="geli"/></property>
</object>
</objects>