范振鈞,鞏小東,王增強(qiáng)
(通化師范學(xué)院 計(jì)算機(jī)學(xué)院,吉林 通化134002)
設(shè)計(jì)模式就是對(duì)象(類 )的集合,它具有解決某一類特定問題的功能,而且是解決這類問題的最優(yōu)方案[1].應(yīng)用設(shè)計(jì)模式可以使軟件具備良好的可靠性、可擴(kuò)展性、可復(fù)用性和可維護(hù)性.
觀察者模式是設(shè)計(jì)模式中的經(jīng)典模式,它定義了對(duì)象間的一對(duì)多依賴關(guān)系.當(dāng)一方的對(duì)象改變狀態(tài)時(shí),所有的依賴者都會(huì)被通知并自動(dòng)被更新[2].在觀察者模式中,被依賴的一方叫做目標(biāo)或主題(Subject),依賴方叫做觀察者(Observers).觀察者模式可以讓多個(gè)觀察者同時(shí)監(jiān)聽同一個(gè)主題,當(dāng)主題對(duì)象發(fā)生變化時(shí),通知所有觀察者,使各個(gè)觀察者能作出相應(yīng)的響應(yīng).主題和觀察者之間是松耦合,主題可隨時(shí)增加或減少觀察者而自身不受影響.觀察者根據(jù)主題狀態(tài)的改變,對(duì)自身的行為和數(shù)據(jù)做相應(yīng)的變化,這種變化對(duì)主題沒有任何影響.使用觀察者模式主要目的就是減少組件間耦合度,減少對(duì)象之間的依賴關(guān)系.
在Observer 模式中有3 種基本的操作, 即注冊(cè)、通知和注銷.
(1)注冊(cè).觀察者類調(diào)用主題的注冊(cè)方法,在主題類中登記.
(2)通知.當(dāng)主題的數(shù)據(jù)變化時(shí),主題向注冊(cè)的觀察者發(fā)送消息.
(3)注銷.當(dāng)觀察者不需要繼續(xù)觀察主題,執(zhí)行主題類的注銷操作,解除了對(duì)主題的觀察.
傳統(tǒng)方法一般都采用接口的方法.抽象觀察者類提供了與主題中的數(shù)據(jù)變化相適應(yīng)的更新方法,所有觀察者類都要實(shí)現(xiàn)這個(gè)接口,用來實(shí)現(xiàn)觀察者自身行為或外觀的變化.主題接口提供了注冊(cè)、注銷以及通知操作等三個(gè)抽象方法,充當(dāng)主題的類必須實(shí)現(xiàn)這三個(gè)方法.傳統(tǒng)的觀察者實(shí)現(xiàn)代碼如下:
Public interface iRaditionalObserver // 觀察者接口
{void ChangeUpdate(object anobject) //觀察者更新方法 }
//主題接口
Public Interface Subject
{ void Logon(RaditionalObserver anObserver);//注冊(cè)}
void logout(IRationalObserver anObserver);//注銷 }
//觀察者實(shí)例
public class Observer:iRaditionalObserver
{ public void ChangeUpdate (object anObject)
{ //觀察者接口中的更新方法, 實(shí)現(xiàn)自身外觀或行為的改變 } }
//主題實(shí)例
public class RaditionalSubject:Subject
protected Hashtable observerContainer= new Hashtable( ) ;//在主題類中有個(gè)列表,作為觀察者的容器來存放觀察者
public void logon( RaditionalObserver anObserver)
{ observerContainer.Add(RaditionalObserver anObserver) //注冊(cè) }
public void logout( RaditionalObserver anObserver)
{ observerContainer.Remove(anObserver) //注銷 }
public void Notify(Object anObject)
{ foreach(RaditionalObserver anObserver in ObserverContainer)
{ anObserver.Notify(anObject) ;//觀察者鏈表的循環(huán)訪問通知.}
在上述觀察者模式實(shí)現(xiàn)方法中,抽象出觀察者接口后,目標(biāo)和觀察者就只是在抽象層面上耦合,也就是說目標(biāo)只是知道觀察者接口,并不知道具體的觀察者的類,從而實(shí)現(xiàn)目標(biāo)類和具體的觀察者類之間解耦,完成了觀察者模式的主要功能.
具體主題類RaditonalSubject中包含了一個(gè)私有的哈希對(duì)象obserContainer,它是一個(gè)保存觀察者對(duì)象的容器.主題類中通過logon方法將觀察者加入到哈希表中實(shí)現(xiàn)注冊(cè)操作,通過logout方法撤銷觀察者和主體的聯(lián)系,實(shí)現(xiàn)注銷操作,當(dāng)主題的狀態(tài)變化時(shí),通過Notify方法遍歷哈希表中的所有觀察者,通知其數(shù)據(jù)的改變,實(shí)現(xiàn)通知操作.在傳統(tǒng)方法中存在著如下缺點(diǎn):
(1)主題類要保存觀察者的列表,從而實(shí)現(xiàn)注冊(cè)、注銷等服務(wù),所以觀察者和觀察者之間還存在著間接的依賴關(guān)系.
(2) 如果一個(gè)被觀察者對(duì)象有很多的觀察者的話,通知會(huì)花費(fèi)很多時(shí)間.因?yàn)榇嬖谥^察者的列表,存在著列表的循環(huán)遍歷,通知的效率取決于列表的規(guī)模,當(dāng)觀察者的數(shù)目很大時(shí),容易形成擁塞.
為了在Observer 模式中實(shí)現(xiàn)進(jìn)一步的低耦合,可以利用.net提供的委托和事件技術(shù)實(shí)現(xiàn)Observer 模式[3].
委托是一個(gè)類,它定義了方法的類型,使得可以將方法當(dāng)作另一個(gè)方法的參數(shù)來進(jìn)行傳遞,這種將方法動(dòng)態(tài)地賦給參數(shù)的做法,可以避免在程序中大量使用選擇語句,使程序具有更好的可擴(kuò)展性.委托可以不知道自己封裝方法所屬類的詳細(xì)信息,只要調(diào)用委托的方法的參數(shù)類型和返回類型與委托的相匹配,該方法就可以被調(diào)用.我們可以充分利用委托的這一重要屬性,結(jié)合事件機(jī)制實(shí)現(xiàn)觀察者模式,進(jìn)一步地降低對(duì)象間的耦合性,將靜態(tài)的組合關(guān)系變?yōu)檫\(yùn)行時(shí)的動(dòng)態(tài)組合關(guān)系.
委托的另一個(gè)重要的屬性是多播.通過多播屬性可以實(shí)現(xiàn)觀察者的注冊(cè)和注銷.
在.net平臺(tái)中,事件就是對(duì)委托的進(jìn)一步封裝,事件是當(dāng)對(duì)象發(fā)生某些變化時(shí),用于向該類的客戶提供通知的一種結(jié)構(gòu).事件基于委托,為委托提供一種發(fā)布訂閱機(jī)制[4],事件可以簡單地分成兩個(gè)部分,事件發(fā)生的類和事件接收處理的類.事件發(fā)生的類就是在這個(gè)類中觸發(fā)了一個(gè)事件,但這個(gè)類并不知道哪個(gè)對(duì)象或方法將會(huì)收到并處理它觸發(fā)的事件,觸發(fā)事件時(shí)調(diào)用的處理程序方法需要定義,其參數(shù)由委托類型來定義.事件發(fā)生的類就可以看做主題,事件接收處理的類作為觀察者.發(fā)送方和接收方之間可以通過委托作為媒介,來完成事件的處理.
(1)實(shí)現(xiàn)方案.①主題類的設(shè)計(jì).在主題類中建立一個(gè)代理, 將更新函數(shù)的簽名傳遞給這個(gè)代理,然后在主題類中用這個(gè)代理聲明一個(gè)事件,定義事件觸發(fā)機(jī)制.②觀察者類的設(shè)計(jì).觀察者類需要提供一個(gè)更新函數(shù),確保該更新函數(shù)的簽名和代理的簽名一致.③注冊(cè)和注銷操作.在程序運(yùn)行時(shí),觀察者類通過+=操作將更新函數(shù)注冊(cè)到主題類中,當(dāng)觀察者不需要觀察主題對(duì)象時(shí),可以從委托中刪除方法調(diào)用實(shí)現(xiàn)注銷操作.
(2)具體實(shí)現(xiàn)例子.通過一個(gè)簡單的實(shí)例來說明.net下觀察者模式的實(shí)現(xiàn)過程.在一個(gè)股票推薦的網(wǎng)站中,當(dāng)注冊(cè)的投資者所投資的股票價(jià)格在股票市場發(fā)生變化時(shí),投資者可以自動(dòng)得到通知.
//聲明委托作為事件與觀察者的中介
Public delegate void delegateUpdate(double price)
//定義主題類
class Stock
{ Public event delegateUpdate handlerPriceChange; //引入事件,使用委托作為事件代理
Protected double price; //股票價(jià)格
Public double Price // Properties
{set //寫屬性
{price=value;
if(handlerPriceChange!=null)//事件引發(fā),通過委托發(fā)給觀察者
delegateUpdate(value);
}
}
class Investor//觀察者角色
{public void Update(double price)//和委托的簽名必須相同
{Console.WriteLine("Price is changed {0}",price);}
|
主程序:
Stock ibm=new stock();//主題類實(shí)例
Investor s=new Investor();//觀察者實(shí)例
Ibm.HandlerPriceChange+=s.Update; //注冊(cè)觀察者s
ibm.HandlrPriceChange+=b.Update; //注冊(cè)觀察者b
//Change price,which notifies investors
ibm.Price=121.00;//主題類觸發(fā)事件,自動(dòng)通知所有注冊(cè)的觀察者
Ibm.HandlerPriceChange-=s.Update; //注銷s
Ibm.HandlrPriceChange-=b.Update; //注銷b
在上述代碼中,主題類和觀察者類都沒有實(shí)現(xiàn)任何接口,主題類只需提供一個(gè)代理和一個(gè)事件,就能將數(shù)據(jù)變化的信息逐一發(fā)送到每個(gè)觀察者對(duì)象,并且還能更加容易的增加新的觀察者對(duì)象,根本不需要考慮它從何處繼承而來,也不需要統(tǒng)一他們的接口調(diào)用方法.觀察者類只需要實(shí)現(xiàn)針對(duì)主題變化的更新方法,并把該方法注冊(cè)到主題類中提供的事件中去,當(dāng)觀察者不需要觀察主題類中的變化時(shí),通過-=操作實(shí)現(xiàn)注銷功能.通過代理和事件機(jī)制,改進(jìn)了傳統(tǒng)方法中的鏈表及相應(yīng)的遍歷操作帶來的缺陷,將靜態(tài)的組合關(guān)系轉(zhuǎn)變?yōu)檫\(yùn)行時(shí)的動(dòng)態(tài)組合關(guān)系,由于沒有使用接口,提高了系統(tǒng)的靈活性.
在.net平臺(tái)下的觀察者模式實(shí)現(xiàn)方案中,注冊(cè)、注銷、通知三種基本操作可以通過委托結(jié)合事件機(jī)制動(dòng)態(tài)進(jìn)行,解除了目標(biāo)對(duì)象對(duì)觀察者的依賴,使目標(biāo)與觀察者的藕合性更低,增加了設(shè)計(jì)的靈活性.
參考文獻(xiàn):
[1]林碧英,曲俊華.設(shè)計(jì)模式在電子商務(wù)交易網(wǎng)站中的應(yīng)用[J].計(jì)算機(jī)系統(tǒng)應(yīng)用,2005(1).
[2]Erich Gamma, Richard Helm, Ralph Johnson.設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)[M].李英軍,馬曉星,蔡敏,等譯.北京: 機(jī)械工業(yè)出版社,2005.
[3]喻金平,劉孝會(huì).C#中的委托在Observer 設(shè)計(jì)模式中的應(yīng)用研究[J].軟件導(dǎo)刊,2010,9(11).
[4]ChrisTlan Nagel .C#高級(jí)編程[M].北京:清華大學(xué)出版社,2011.