模式發現者
:
蔡超
北京天融信,軟件架構師
SUN certified Enterprise Architect
Microsoft certified Solution Developer
IBM certified RUP Specialist
聯系方式
:cai_chao@topsec.com.cn,chaocai2001@yahoo.com.cn
010-82776427
語境
在基于異步通信方式的系統中,實現模塊的同步調用。
問題
消息隊列已經成為目前很多軟件選用的通訊方式,消息模式使得不同分布式組件間的耦合性較為松散,提高了系統的可維護性和可擴充性。但是有時我們希望在這種情況下能夠模擬同步的調用方式,希望能夠通過一個組件透明的幫我們實現這種異步和同步調用的轉換。
解決方案
圖表
1
基于消息的分布式系統
上圖是一種常見的基于消息的分布系統結構,系統中包含兩條隊列。命令隊列用于傳送模塊間相互調用的命令對象,響應隊列用于傳送命令處理后的響應結果對象。
在通常情況下消息隊列上的模塊會以異步的方式工作,這種情況下模塊通常不關心命令執行后的狀況,常常是不須返回值的。
為了模擬同步調用我們通過一個
SynProxy
組件來幫助我們完成由異步到同步調用的模擬。使得
Client
對其他模塊的調好像是同步的一樣。
SynProxy
組件的結構如下:
圖表
2
SynProxy
的靜態結構
SynProxy:
組織完成異步到同步調用的轉換
WaitingQueue:
存放還未獲得執行返回值的
Command
對象
UIDCreator:
產生能夠唯一標示每個
Command
對象的
UID
ResponseReceiver:
監聽響應隊列中的響應消息,它在獨立的線程中運行
Command
:命令對象
Response
:響應對象
?
圖表
3
將異同步調用轉化成同步調用的過程
部分解釋:
(
以上以在
JAVA
環境中為例
)
Wait
表示調用
Command
的
wait
方法阻塞住當前線程
Command
執行模塊在執行了響應操作后生產
Response
對象用于放回結果,
Response
對象中的
UID
屬性應該與處理的
Command
的
UID
的值相同
Notify
表示調用
Command
的
notify
方法喚醒被阻塞的線程
上面過程中的
wait
設置了阻塞的超時時間(來自于
Command
對象的屬性值)
蔡
超
SCEA
,
SCBCD
,
MCSD
北京天融信軟件架構師
?SUN,Microsoft培訓中心特邀高端教師
常年提供架構咨詢服務
chaocai2001@yahoo.com.cn
,
010-82776427
?
4+1
視圖與
UML
軟件架構設計已經逐漸成為現代軟件開發過程的核心,然而能夠清晰表明架構設計并不是一件容易的事,就面向對象開發而言,
RUP
的
4+1
視圖已在架構設計的撰寫中得到了廣泛的應用和認可。
對于
4+1 view
的描述有幾個不同版本(或包含的視圖不同,或視圖的名稱不同),文中以
Philippe Kruchten, November 1995
提出的
4+1
視圖為準。
4+1
視圖包括:邏輯視圖(
Logic View
),開發視圖(
Develop View
),進程視圖(
Process View
),物理視圖(
Physical View
)和場景視圖(
Scenarios
)。
?
視圖間的關系
?
4+1
視圖不僅便于我們記錄架構設計,實際上它也指導了我們進行架構設計活動的部分過程。
通常我們選擇
UML
來表現各種視圖,以下列出了
UML
和各視圖的對應關系
4+1
視圖
??????
?????????????????????????
??UML
場景視圖
????????
???????????????????use case
邏輯視圖
???????????????????????????
類圖
開發視圖
???????????????????????????
類圖,組件圖
進程視圖
???????????????????????????
無完全對應
部署視圖
???????????????????????????
部署圖
在架構設計穩定中通常不會給出較多的用例描述,這些是在需求穩定中定義。但是往往架構文檔會選擇一些用例,列入文檔中,這些用例和一些非功能性需求一起用以證明架構的有效和正確性。在邏輯視圖中用例的實現是必不可少的一節,盡管架構設計更關注非功能性需求。
融入
MDA
的思想
對于邏輯視圖和開發視圖所應包含的內容常常會覺得很難區分兩者間的明顯界限。邏輯視圖包含更多的分析模型與實現技術本身相關性應該較少,如業務對象模型及其擴展。而開發視圖則會與實現技術緊密相關。
隨著
MDA
思想的推廣,在架構設計文檔的撰寫方面也產生了影響,我們不難把
MDA
的
PIM
和邏輯視圖聯系起來,而把
MDA
中的
PSM
和開發視圖聯系起來。
在編寫邏輯視圖是我們應該描述與技術平臺無關的模型,而開發視圖則描述與實現技術平臺相關的模型。
如在邏輯視圖中表現的某些實體類,我們會在開發視圖中轉換為
EJB
組件(實體
Bean
)。
這種做法不僅有利于我們編寫架構設計文檔,同時更是一種好的架構設計思考流程。
?
關于 MVC 模式中的通知機制
蔡超
1 MVC 簡介
一般應用中用戶界面的變化相對較為頻繁 , 有時需要支持多種表現模式(如 WEB 客戶端和 GUI 客戶端),但是數據和業務邏輯相對保持穩定。
MVC(Model-View-Controller) 模式是一種常用的設計模式 ,MVC 將模型 , 顯示和控制進行了分離,可以使得應用更加方便實現對多種表現模式的支持及降低表現形式修改對整體系統的影響。由于本文重點討論的是 MVC 中的通知機制,至于 MVC 的其它內容可以參考其它相關文檔。
圖表 1 MVC 模式
2 MVC 的通知機制
上圖是在 SUN 的 J2EE BluePrints 中關于 MVC 模式的描述 , 在實現 MVC 模式時首先應該注意的是模型與視圖之間的關系。在這些關系中尤其值得大家注意的是模型通知視圖,如果不能正確的設計這個通知機制(模型與視圖之間關聯實現通知)便會完全違背 MVC 的設計初衷。 MVC 模式的其中一個目的在于使模式獨立與視圖,然而不正確的理解和設計通知機制會導致模型和試圖的依賴性。
2.1 采用 Observer 模式實現通知機制
既要實現模式到視圖的通知機制,同時有要確保實現模型與視圖的分離。通常我們可以通過Observer模式來實現這樣的通知機制。
圖表2 Observer模式
視圖實現 Observer 接口,并向模型注冊,模型通過調用所維護的觀察者的實例調用 Update 方法來通知視圖進行刷新。可見, Observer 接口有效的實現了模型和視圖間的耦合性的分離。
2.2 模型通知視圖還是控制器通知視圖
圖表 3 基于 MVC 的 J2EE 應用
在將 MVC 模式應用于的總體結構時,常常會有是模型通知視圖還是控制器通知視圖的問題,其實我認為這個問題完全取決于對系統各個部分的劃分和理解,如果我們把模型層更多劃分為數據實體 ( 如: Entity Bean) 則可能會發現,其實我們的通知機制是不能由模型部分來完成的,而是由我們的控制器來完成的。這種劃分好象有些違背了 MVC 模式,但實事上特別是一些想要同時支持 B/S 和 C/S 的 J2EE 應用,控制器和模式通知機制常常有較大的耦合性( C/S 結構中,客戶完全通過會話 Bean 來完成業務),可能有時在一起實現更好。
總之,更好的內聚性和更松散的耦合性才是架構設計的重點,應該做出適合自身應用的 MVC 架構。
3 結束語
MVC 模式的關鍵在于分離易變和不易變部分間的耦合性,所以在應用 MVC 模式時一定要注意解耦才是關鍵,同時一定要更據具體的使用環境進行調整,不要生搬硬套,如 Microsoft 的 MFC 采用的 Document-view 就是 MVC 的變體,它把控制器合并到視圖中,這是因為考慮了視圖與控制器緊耦合的影響。
【參考文獻】
1. Sun Microsystem , J2EE BluePrints
2. GOF,Design Patterns, 機械工業出版社, 2002
作者: 蔡超
依賴注入(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