模式發(fā)現(xiàn)者
:
蔡超
北京天融信,軟件架構(gòu)師
SUN certified Enterprise Architect
Microsoft certified Solution Developer
IBM certified RUP Specialist
聯(lián)系方式
:cai_chao@topsec.com.cn,chaocai2001@yahoo.com.cn
010-82776427
語境
在基于異步通信方式的系統(tǒng)中,實現(xiàn)模塊的同步調(diào)用。
問題
消息隊列已經(jīng)成為目前很多軟件選用的通訊方式,消息模式使得不同分布式組件間的耦合性較為松散,提高了系統(tǒng)的可維護性和可擴充性。但是有時我們希望在這種情況下能夠模擬同步的調(diào)用方式,希望能夠通過一個組件透明的幫我們實現(xiàn)這種異步和同步調(diào)用的轉(zhuǎn)換。
解決方案
圖表
1
基于消息的分布式系統(tǒng)
上圖是一種常見的基于消息的分布系統(tǒng)結(jié)構(gòu),系統(tǒng)中包含兩條隊列。命令隊列用于傳送模塊間相互調(diào)用的命令對象,響應(yīng)隊列用于傳送命令處理后的響應(yīng)結(jié)果對象。
在通常情況下消息隊列上的模塊會以異步的方式工作,這種情況下模塊通常不關(guān)心命令執(zhí)行后的狀況,常常是不須返回值的。
為了模擬同步調(diào)用我們通過一個
SynProxy
組件來幫助我們完成由異步到同步調(diào)用的模擬。使得
Client
對其他模塊的調(diào)好像是同步的一樣。
SynProxy
組件的結(jié)構(gòu)如下:
圖表
2
SynProxy
的靜態(tài)結(jié)構(gòu)
SynProxy:
組織完成異步到同步調(diào)用的轉(zhuǎn)換
WaitingQueue:
存放還未獲得執(zhí)行返回值的
Command
對象
UIDCreator:
產(chǎn)生能夠唯一標(biāo)示每個
Command
對象的
UID
ResponseReceiver:
監(jiān)聽響應(yīng)隊列中的響應(yīng)消息,它在獨立的線程中運行
Command
:命令對象
Response
:響應(yīng)對象
?
圖表
3
將異同步調(diào)用轉(zhuǎn)化成同步調(diào)用的過程
部分解釋:
(
以上以在
JAVA
環(huán)境中為例
)
Wait
表示調(diào)用
Command
的
wait
方法阻塞住當(dāng)前線程
Command
執(zhí)行模塊在執(zhí)行了響應(yīng)操作后生產(chǎn)
Response
對象用于放回結(jié)果,
Response
對象中的
UID
屬性應(yīng)該與處理的
Command
的
UID
的值相同
Notify
表示調(diào)用
Command
的
notify
方法喚醒被阻塞的線程
上面過程中的
wait
設(shè)置了阻塞的超時時間(來自于
Command
對象的屬性值)
蔡
超
SCEA
,
SCBCD
,
MCSD 北京天融信軟件架構(gòu)師 ?SUN,Microsoft培訓(xùn)中心特邀高端教師
常年提供架構(gòu)咨詢服務(wù)
chaocai2001@yahoo.com.cn
,
010-82776427
?
4+1
視圖與
UML
軟件架構(gòu)設(shè)計已經(jīng)逐漸成為現(xiàn)代軟件開發(fā)過程的核心,然而能夠清晰表明架構(gòu)設(shè)計并不是一件容易的事,就面向?qū)ο箝_發(fā)而言,
RUP
的
4+1
視圖已在架構(gòu)設(shè)計的撰寫中得到了廣泛的應(yīng)用和認(rèn)可。
對于
4+1 view
的描述有幾個不同版本(或包含的視圖不同,或視圖的名稱不同),文中以
Philippe Kruchten, November 1995
提出的
4+1
視圖為準(zhǔn)。
4+1
視圖包括:邏輯視圖(
Logic View
),開發(fā)視圖(
Develop View
),進程視圖(
Process View
),物理視圖(
Physical View
)和場景視圖(
Scenarios
)。
?
視圖間的關(guān)系
?
4+1
視圖不僅便于我們記錄架構(gòu)設(shè)計,實際上它也指導(dǎo)了我們進行架構(gòu)設(shè)計活動的部分過程。
通常我們選擇
UML
來表現(xiàn)各種視圖,以下列出了
UML
和各視圖的對應(yīng)關(guān)系
4+1
視圖
??????
?????????????????????????
?? UML
場景視圖
????????
??????????????????? use case
邏輯視圖
???????????????????????????
類圖
開發(fā)視圖
???????????????????????????
類圖,組件圖
進程視圖
???????????????????????????
無完全對應(yīng)
部署視圖
???????????????????????????
部署圖
在架構(gòu)設(shè)計穩(wěn)定中通常不會給出較多的用例描述,這些是在需求穩(wěn)定中定義。但是往往架構(gòu)文檔會選擇一些用例,列入文檔中,這些用例和一些非功能性需求一起用以證明架構(gòu)的有效和正確性。在邏輯視圖中用例的實現(xiàn)是必不可少的一節(jié),盡管架構(gòu)設(shè)計更關(guān)注非功能性需求。
融入
MDA
的思想
對于邏輯視圖和開發(fā)視圖所應(yīng)包含的內(nèi)容常常會覺得很難區(qū)分兩者間的明顯界限。邏輯視圖包含更多的分析模型與實現(xiàn)技術(shù)本身相關(guān)性應(yīng)該較少,如業(yè)務(wù)對象模型及其擴展。而開發(fā)視圖則會與實現(xiàn)技術(shù)緊密相關(guān)。
隨著
MDA
思想的推廣,在架構(gòu)設(shè)計文檔的撰寫方面也產(chǎn)生了影響,我們不難把
MDA
的
PIM
和邏輯視圖聯(lián)系起來,而把
MDA
中的
PSM
和開發(fā)視圖聯(lián)系起來。
在編寫邏輯視圖是我們應(yīng)該描述與技術(shù)平臺無關(guān)的模型,而開發(fā)視圖則描述與實現(xiàn)技術(shù)平臺相關(guān)的模型。
如在邏輯視圖中表現(xiàn)的某些實體類,我們會在開發(fā)視圖中轉(zhuǎn)換為
EJB
組件(實體
Bean
)。
這種做法不僅有利于我們編寫架構(gòu)設(shè)計文檔,同時更是一種好的架構(gòu)設(shè)計思考流程。
?
關(guān)于 MVC 模式中的通知機制
蔡超
1 MVC 簡介
一般應(yīng)用中用戶界面的變化相對較為頻繁 , 有時需要支持多種表現(xiàn)模式(如 WEB 客戶端和 GUI 客戶端),但是數(shù)據(jù)和業(yè)務(wù)邏輯相對保持穩(wěn)定。
MVC(Model-View-Controller) 模式是一種常用的設(shè)計模式 ,MVC 將模型 , 顯示和控制進行了分離,可以使得應(yīng)用更加方便實現(xiàn)對多種表現(xiàn)模式的支持及降低表現(xiàn)形式修改對整體系統(tǒng)的影響。由于本文重點討論的是 MVC 中的通知機制,至于 MVC 的其它內(nèi)容可以參考其它相關(guān)文檔。
圖表 1 MVC 模式
2 MVC 的通知機制
上圖是在 SUN 的 J2EE BluePrints 中關(guān)于 MVC 模式的描述 , 在實現(xiàn) MVC 模式時首先應(yīng)該注意的是模型與視圖之間的關(guān)系。在這些關(guān)系中尤其值得大家注意的是模型通知視圖,如果不能正確的設(shè)計這個通知機制(模型與視圖之間關(guān)聯(lián)實現(xiàn)通知)便會完全違背 MVC 的設(shè)計初衷。 MVC 模式的其中一個目的在于使模式獨立與視圖,然而不正確的理解和設(shè)計通知機制會導(dǎo)致模型和試圖的依賴性。
2.1 采用 Observer 模式實現(xiàn)通知機制
既要實現(xiàn)模式到視圖的通知機制,同時有要確保實現(xiàn)模型與視圖的分離。通常我們可以通過Observer 模式來實現(xiàn)這樣的通知機制。
圖表2 Observer模式
視圖實現(xiàn) Observer 接口,并向模型注冊,模型通過調(diào)用所維護的觀察者的實例調(diào)用 Update 方法來通知視圖進行刷新。可見, Observer 接口有效的實現(xiàn)了模型和視圖間的耦合性的分離。
2.2 模型通知視圖還是控制器通知視圖
圖表 3 基于 MVC 的 J2EE 應(yīng)用
在將 MVC 模式應(yīng)用于的總體結(jié)構(gòu)時,常常會有是模型通知視圖還是控制器通知視圖的問題,其實我認(rèn)為這個問題完全取決于對系統(tǒng)各個部分的劃分和理解,如果我們把模型層更多劃分為數(shù)據(jù)實體 ( 如: Entity Bean) 則可能會發(fā)現(xiàn),其實我們的通知機制是不能由模型部分來完成的,而是由我們的控制器來完成的。這種劃分好象有些違背了 MVC 模式,但實事上特別是一些想要同時支持 B/S 和 C/S 的 J2EE 應(yīng)用,控制器和模式通知機制常常有較大的耦合性( C/S 結(jié)構(gòu)中,客戶完全通過會話 Bean 來完成業(yè)務(wù)),可能有時在一起實現(xiàn)更好。
總之,更好的內(nèi)聚性和更松散的耦合性才是架構(gòu)設(shè)計的重點,應(yīng)該做出適合自身應(yīng)用的 MVC 架構(gòu)。
3 結(jié)束語
MVC 模式的關(guān)鍵在于分離易變和不易變部分間的耦合性,所以在應(yīng)用 MVC 模式時一定要注意解耦才是關(guān)鍵,同時一定要更據(jù)具體的使用環(huán)境進行調(diào)整,不要生搬硬套,如 Microsoft 的 MFC 采用的 Document-view 就是 MVC 的變體,它把控制器合并到視圖中,這是因為考慮了視圖與控制器緊耦合的影響。
【參考文獻(xiàn)】
1. Sun Microsystem , J2EE BluePrints
2. GOF,Design Patterns, 機械工業(yè)出版社, 2002
作者: 蔡超
依賴注入( Dependency Injection ) 模式的特點分析與實現(xiàn)
――構(gòu)造子注入 ( Constructor Injection ) 模式的分析與實現(xiàn)
蔡 超
( chaocai2001@yahoo.com.cn )
摘要 :本文對 IoC 模式、依賴注入 (Dependency Injection) 模式做了簡要介紹,文中分析構(gòu)造子注入模式與其他模式相比較的優(yōu)勢和特點,并給出了在 JAVA 中實現(xiàn)該模式的方法。
1 引言
IoC ( Inversion of Control )模式以被目前的輕量級容器所廣泛應(yīng)用,通過 IoC 模式這些容器幫助開發(fā)者將來自不同項目的組件裝配成一個內(nèi)聚的應(yīng)用程序。輕量級的 IoC 容器(如 Spring 、 pico-container )雖然為我們的開發(fā)提供了很大的便利,但是在很多情況下這些輕量級容器所提供的功能并不一定非常適合我們的需要,也許這些容器的功能過于龐大了,或者所提供的功能缺乏對特定應(yīng)用的針對性,或者我們需要更高的運行效率,這時我們可以在了解 IoC 的原理的基礎(chǔ)上利用 JAVA 的反射機制自己實現(xiàn)靈活的、可擴展的組件機制。
2 IoC 與依賴注入( Dependency Injection )模式簡介
與 GoF 的設(shè)計模式相同, IoC 模式同樣是關(guān)注重用性,但與 GoF 模式不同的是 IoC 模式更加關(guān)注二進制級的重用性和可擴展性,即可以直接通過二進制級進行擴充,復(fù)用的模塊通常被稱為組件或者插件,組件和插件都是在運行時進行裝載的。
GoF 的設(shè)計模式中我們大量看到的是面向接口編程: Interface Driven Design 接口驅(qū)動,接口驅(qū)動有很多好處,可以提供不同靈活的子類實現(xiàn),增加代碼穩(wěn)定和健壯性等等,但是接口一定是需要實現(xiàn)的,也就是如下語句遲早要執(zhí)行:
AInterface a = new AInterfaceImp();
由于以上的代碼被寫入了調(diào)用者程序中,同時象 AinterfaceImp 這樣的接口的實現(xiàn)類是在編譯時被裝載的,如果以后想加入新的接口實現(xiàn)類則必須修改調(diào)用者的代碼。
IoC 模式與以上情況不同,接口的實現(xiàn)類是在運行時被裝載的,這樣即使以后新添加了接口實現(xiàn)類是也不需修改調(diào)用者的代碼(可以通過特定的方式來定位新增的實現(xiàn)類,如配置文件指定)。 IoC 英文為 Inversion of Control ,即反轉(zhuǎn)模式,這里有著名的好萊塢理論:你呆著別動,到時我會找你。
IoC 模式可以延緩接口的實現(xiàn),根據(jù)需要實現(xiàn),有個比喻:接口如同空的模型套,在必要時,需要向模型套注射石膏,這樣才能成為一個模型實體,因此, 對于這些新生的容器,它們反轉(zhuǎn)的是“如何定位插件的具體實現(xiàn)”。因此, Martin Fowler 給這種模式起了一個形象的名稱 “依賴注入” ( Dependency Injection ) 。
圖表 1 采用 Dependency Injection 前后的依賴關(guān)系變化
依賴注入的形式主要有三種,分別將它們叫做構(gòu)造子注入( Constructor Injection )、設(shè)值方法注入( Setter Injection )和接口注入( Interface Injection )。
這三種方式在Martin Fowler的《 Inversion of Control Containers and the Dependency Injection pattern 》中都給出了詳細(xì)的定義及說明,本文就不再贅述了,下面的內(nèi)容將著重介紹 構(gòu)造子注入模式的特點及實現(xiàn)方法。
3 構(gòu)造子注入模式的特點及實現(xiàn)
3.1 構(gòu)造子注入模式的特點
通常情況下設(shè) 值 方法注入和接口注入較易于被開發(fā)人員接受,而構(gòu)造子注入則應(yīng)用較少,實際上構(gòu)造子注入具有很多其他兩者所不具有的優(yōu)勢:
1 構(gòu)造子注入形成了一種更強的依賴契約
2 可以獲得更加簡明的代碼
3 更加簡明的依賴聲明機制,無須定義 XML 配置文件或設(shè) 值 方法
4 更加符合接口與實現(xiàn)分離的組件特征,組件接口表明能夠向其它組件提供的服務(wù),而實現(xiàn)則應(yīng)該是所提供服務(wù)的實現(xiàn)應(yīng)該與服務(wù)契約無關(guān)(即不應(yīng)包含用于獲得依賴的設(shè)值方法等)。
5 不會出現(xiàn)不確定的狀態(tài)。在設(shè)值方法注入中,由于并不是所有的設(shè)值方法( setter )都一定會被調(diào)用的,所以會有不確定狀態(tài)。
從以上幾點我們還可以分析出構(gòu)造子注入對于組件代碼的入侵性遠(yuǎn)小于其它兩種模式 ( 接口注入使得組件必須實現(xiàn)特定接口,設(shè)值方法同樣要求組件提供特定的 setter 方法 ) ,代碼更加易于維護 。
圖表 2 示例中類的關(guān)系
Client 的實現(xiàn)依賴于接口 A 、 B 和 C 的實現(xiàn),但是為了提供系統(tǒng)更好的靈活性和可擴展性,各接口的實現(xiàn)以組件的方式利用 java 的反射機制進行運行時裝載,注意到組件間可能會存在某種依賴關(guān)系,例如組件 AX 依賴與接口 B 的實現(xiàn)類,而這中依賴關(guān)系必須在運行時動態(tài)注入,組件為了告訴組件的調(diào)用者這種依賴關(guān)系以便注入,可以使用上文提到的各種模式:
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 使用設(shè)值注入模式
public class AImp implements A {
…
public void setB(B bImp);
public void setC(C cImp);
…
}
3 使用構(gòu)造子注入模式
public class AImp implements A {
…
public AImp(B bImp, C cImp){
…
}
…
}
由以上實例可以清楚的看出采用構(gòu)造子注入模式的實現(xiàn)組件代碼最為簡單,且所受的入侵性最小。
3.2 在 JAVA 中實現(xiàn)構(gòu)造子注入模式
在 java 及 .NET 這樣具有反射功能的語言中實現(xiàn)類型的運行時載入并不復(fù)雜,只要通過 Class.forName 或生成自己的 ClassLoader 就可以實現(xiàn)。
同樣我們可以通過反射機制獲取組件構(gòu)造函數(shù)的參數(shù),注入相應(yīng)接口的實現(xiàn),作者將此過程進行了封裝,以下是代碼:
public class RefectHelper {
public Object ConstructorHelper(String className,ConstructorParamDeal pd) throws Exception{
try{
//獲取類中的構(gòu)造函數(shù)
Constructor[] constructs=Class.forName(className).getConstructors(); // 實現(xiàn)中默認(rèn)使用第一個構(gòu)造函數(shù)類創(chuàng)建實例
Class [] classes=constructs[0].getParameterTypes();
//獲取要注入的參數(shù)實例
Object []obj=pd.dealParam(classes);
//創(chuàng)建實例
return constructs[0].newInstance(obj);
}catch(Exception e){
throw e;
}
}
}
/**
* 構(gòu)造函數(shù)參數(shù)注入
**/
public interface ConstructorParamDeal {
/**
*根據(jù)構(gòu)造函數(shù)中參數(shù)的類型注入 , 相應(yīng)的實現(xiàn)
@param classes 構(gòu)造函數(shù)的參數(shù)類型
◎ return 注入構(gòu)造函數(shù)的參數(shù)實現(xiàn)
**/
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 用于利用反射機制枚舉出載入類的構(gòu)造函數(shù)及構(gòu)造函數(shù)的參數(shù)的類型,至于不同類型注入什么樣的實例則由 ContructorParamDeal 的實現(xiàn)者來決定, ContructorParamDeal 的實現(xiàn)者同樣可以以組件的形式在運行時動態(tài)載入。由于組件間的依賴關(guān)系的制約,所以組件實例化的順序需要特別考慮。
4 結(jié)束語
三種依賴注入模式各有其特點和優(yōu)勢,只有充分理解這些模式間的不同,才能為自己的應(yīng)用選擇正確的依賴注入模式,文中介紹的構(gòu)造子注入模式實現(xiàn)方法,在使用其他具有反射功能的語言(如: .NET )時同樣可以參考。
[參考文獻(xiàn) ]
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