訂閱發布模式定義了一種一對多的依賴關系,讓多個訂閱者對象同時監聽某一個主題對象。這個主題對象在自身狀態變化時,會通知所有訂閱者對象,使它們能夠自動更新自己的狀態。
將一個系統分割成一系列相互協作的類有一個很不好的副作用,那就是需要維護相應對象間的一致性,這樣會給維護、擴展和重用都帶來不便。當一個對象的改變需要同時改變其他對象,而且它不知道具體有多少對象需要改變時,就可以使用訂閱發布模式了。
一個抽象模型有兩個方面,其中一方面依賴於另一方面,這時訂閱發布模式可以將這兩者封裝在獨立的對象中,使它們各自獨立地改變和復用。訂閱發布模式所做的工作其實就是在解耦合。讓耦合的雙方都依賴於抽象,而不是依賴於具體,從而使得各自的變化都不會影響另一邊的變化。
在我們日常寫程序時,經常遇到下面這種情況:
public void 有告警信息產生() { 刷新界面(); 更新數據庫(); 給管理員發Mail(); ……………………………… }
當有告警信息產生時,依次要去執行:刷新界面()、更新數據庫()、給管理員發Mail()等操作。表面上看代碼寫得很工整,其實這里面問題多多:
- 首先,這完全是面向過程開發,根本不適合大型項目。
- 第二,代碼維護量太大。設想一下,如果產生告警后要執行10多個操作,那這將是個多么大,多少復雜的類呀,時間一長,可能連開發者自己都不知道如何去維護了。
- 第三,擴展性差。如果產生告警后,要增加一個聲音提示()功能,怎么辦呢?沒錯,只能加在有告警信息產生()這個函數中,這樣一來,就違反了“開放-關閉原則”。而且修改了原有的函數,那么在測試時,除了要測新增功能外,還要做原功能的回歸測試;在一個大型項目中,做一次回歸測試可能要花費大約兩周左右的時間,而且前提是新增功能沒有影響原來功能及產生新的bug。
那么如何把有告警信息產生()函數同其他函數進行解耦合呢?別着急,下面就介紹今天的主角----訂閱發布模式。見下圖:
上面的流程就是對有告警信息產生()這個函數的描述。我們要做的,就是把產生告警和它需要通知的事件進行解耦,讓它們之間沒有相互依賴的關系,解耦合圖如下:
事件觸發者被抽象出來,稱為消息發布者,即圖中的P。事件接受都被抽象出來,稱為消息訂閱者,即圖中的S。P與S之間通過S.P(即訂閱器)連接。這樣就實現了P與S的解耦。首先,P就把消息發送到指定的訂閱器上,從始至終,它並不知道也不關心要把消息發向哪個S。S如果想接收消息,就要向訂閱器進行訂閱,訂閱成功后,S就可以接收來自S.P的消息了,從始至終,S並不知道也不關心消息來源於哪個具體的P。同理,S還可以向S.P進行退訂操作,成功退訂后,S就無法接收到來自指定S.P的消息了。這樣就完美的解決了P與S之間的解耦。
等等,好像還有一個問題。從圖上看,雖然P與S之間完成了解耦,但是P與S.P,S與S.P之間不就又耦合上了嗎?其實這個問題好解決,想想我們上篇的裝飾模式是怎么解耦的?對,就接口。這里我們同樣使用接口來解決P與S.P和S與S.P之間的解耦,同時,使用delegate來解決多訂閱多發布的機制。
下面給出實現代碼。由於訂閱發布模式涉及P, S.P和S三部份內容,所以代碼比較多,也比較長。請大家耐心閱讀。
首先,為了實現P與S.P, S與S.P之間的解耦,我們需要定義兩個接口文件
ISubscribe.cs namespace TJVictor.DesignPattern.SubscribePublish { //定義訂閱事件 public delegate void SubscribeHandle(string str); //定義訂閱接口 public interface ISubscribe { event SubscribeHandle SubscribeEvent; } } IPublish namespace TJVictor.DesignPattern.SubscribePublish { //定義發布事件 public delegate void PublishHandle(string str); //定義發布接口 public interface IPublish { event PublishHandle PublishEvent; void Notify(string str); } } 然后我們來設計訂閱器。顯然訂閱器要實現雙向解耦,就一定要繼承上面兩個接口,這也是我為什么用接口不用抽象類的原因(類是單繼承)。 namespace TJVictor.DesignPattern.SubscribePublish { public class SubPubComponet : ISubscribe, IPublish { private string _subName; public SubPubComponet(string subName) { this._subName = subName; PublishEvent += new PublishHandle(Notify); } #region ISubscribe Members event SubscribeHandle subscribeEvent; event SubscribeHandle ISubscribe.SubscribeEvent { add { subscribeEvent += value; } remove { subscribeEvent -= value; } } #endregion #region IPublish Members public PublishHandle PublishEvent; event PublishHandle IPublish.PublishEvent { add { PublishEvent += value; } remove { PublishEvent -= value; } } #endregion public void Notify(string str) { if (subscribeEvent != null) subscribeEvent.Invoke(string.Format("消息來源{0}:消息內容:{1}", _subName, str)); } } } 接下來是設計訂閱者S。S類中使用了ISubscribe來與S.P進行解耦。代碼如下: namespace TJVictor.DesignPattern.SubscribePublish { public class Subscriber { private string _subscriberName; public Subscriber(string subscriberName) { this._subscriberName = subscriberName; } public ISubscribe AddSubscribe { set { value.SubscribeEvent += Show; } } public ISubscribe RemoveSubscribe { set { value.SubscribeEvent -= Show; } } private void Show(string str) { Console.WriteLine(string.Format("我是{0},我收到訂閱的消息是:{1}", _subscriberName, str)); } } } 最后是發布者P,繼承IPublish來對S.P發布消息通知。 namespace TJVictor.DesignPattern.SubscribePublish { public class Publisher:IPublish { private string _publisherName; public Publisher(string publisherName) { this._publisherName = publisherName; } private event PublishHandle PublishEvent; event PublishHandle IPublish.PublishEvent { add { PublishEvent += value; } remove { PublishEvent -= value; } } public void Notify(string str) { if (PublishEvent != null) PublishEvent.Invoke(string.Format("我是{0},我發布{1}消息", _publisherName, str)); } } } 至此,一個簡單的訂閱發布模式已經完成了。下面是調用代碼及運行結果。調用代碼模擬了圖2中的訂閱發布關系,大家可以從代碼,運行結果和示例圖三方面對照着看。 #region TJVictor.DesignPattern.SubscribePublish //新建兩個訂閱器 SubPubComponet subPubComponet1 = new SubPubComponet("訂閱器1"); SubPubComponet subPubComponet2 = new SubPubComponet("訂閱器2"); //新建兩個發布者 IPublish publisher1 = new Publisher("TJVictor1"); IPublish publisher2 = new Publisher("TJVictor2"); //與訂閱器關聯 publisher1.PublishEvent += subPubComponet1.PublishEvent; publisher1.PublishEvent += subPubComponet2.PublishEvent; publisher2.PublishEvent += subPubComponet2.PublishEvent; //新建兩個訂閱者 Subscriber s1 = new Subscriber("訂閱人1"); Subscriber s2 = new Subscriber("訂閱人2"); //進行訂閱 s1.AddSubscribe = subPubComponet1; s1.AddSubscribe = subPubComponet2; s2.AddSubscribe = subPubComponet2; //發布者發布消息 publisher1.Notify("博客1"); publisher2.Notify("博客2"); //發送結束符號 Console.WriteLine("".PadRight(50,'-')); //s1取消對訂閱器2的訂閱 s1.RemoveSubscribe = subPubComponet2; //發布者發布消息 publisher1.Notify("博客1"); publisher2.Notify("博客2"); //發送結束符號 Console.WriteLine("".PadRight(50, '-')); #endregion #region Console.ReadLine(); Console.ReadLine(); #endregion
運行結果圖:
如需轉載,請注明本文原創自CSDN TJVictor專欄:http://blog.csdn.net/tjvictor