一、引言
今天是2017年11月份的最后一天,也就是2017年11月30日,利用今天再寫一個模式,爭取下個月(也就是12月份)把所有的模式寫完,2018年,新的一年寫一些新的東西。今天我們開始講“行為型”設計模式的第四個模式,該模式是【觀察者模式】,英文名稱是:Observer Pattern。還是老套路,先從名字上來看看。“觀察者模式”我第一次看到這個名稱,我的理解是,既然有“觀察者”,那肯定就有“被觀察者”了,“觀察者”監視着“被觀察者”,如果“被觀察者”有所行動,“觀察者”就會做出相應的動作來回應,哈哈,聽起來是不是有點像“諜戰”的味道。我所說的諜戰不是天朝內的那種,比如:手撕鬼子,我說的是“諜影重重”的那類優秀影片,大家懂得。“觀察者模式”在現實生活中,實例其實是很多的,比如:八九十年代我們訂閱的報紙,我們會定期收到報紙,因為我們訂閱了。銀行可以給儲戶發手機短信,也是“觀察者模式”很好的使用的例子,因為我們訂閱了銀行的短信業務,當我們賬戶余額發生變化就會收到通知,還有很多,我就不一一列舉了,發揮大家的想象吧。好了,接下來,就讓我們看看該模式具體是怎么實現的吧。
二、觀察者模式的詳細介紹
2.1、動機(Motivate)
在軟件構建過程中,我們需要為某些對象建立一種“通知依賴關系”——一個對象(目標對象)的狀態發生改變,所有的依賴對象(觀察者對象)都將得到通知。如果這樣的依賴關系過於緊密,將使軟件不能很好地抵御變化。
使用面向對象技術,可以將這種依賴關系弱化,並形成一種穩定的依賴關系。從而實現軟件體系結構的松耦合。
2.2、意圖(Intent)
定義對象間的一種一對多的依賴關系,以便當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並自動更新。 ——《設計模式》GoF
2.3、結構圖
2.4、模式的組成
可以看出,在觀察者模式的結構圖有以下角色:
(1)、抽象主題角色(Subject):抽象主題把所有觀察者對象的引用保存在一個列表中,並提供增加和刪除觀察者對象的操作,抽象主題角色又叫做抽象被觀察者角色,一般由抽象類或接口實現。
(2)、抽象觀察者角色(Observer):為所有具體觀察者定義一個接口,在得到主題通知時更新自己,一般由抽象類或接口實現。
(3)、具體主題角色(ConcreteSubject):實現抽象主題接口,具體主題角色又叫做具體被觀察者角色。
(4)、具體觀察者角色(ConcreteObserver):實現抽象觀察者角色所要求的接口,以便使自身狀態與主題的狀態相協調。
2.5、觀察者模式的代碼實現
觀察者模式在顯示生活中也有類似的例子,比如:我們訂閱銀行短信業務,當我們賬戶發生改變,我們就會收到相應的短信。類似的還有微信訂閱號,今天我們就以銀行給我發送短信當我們賬戶余額發生變化的時候為例來講講觀察者模式的實現,很簡單,現實生活正例子也很多,理解起來也很容易。我們看代碼吧,實現代碼如下:
1 namespace 觀察者模式的實現 2 { 3 //銀行短信系統抽象接口,是被觀察者--該類型相當於抽象主體角色Subject 4 public abstract class BankMessageSystem 5 { 6 protected IList<Depositor> observers; 7 8 //構造函數初始化觀察者列表實例 9 protected BankMessageSystem() 10 { 11 observers = new List<Depositor>(); 12 } 13 14 //增加預約儲戶 15 public abstract void Add(Depositor depositor); 16 17 //刪除預約儲戶 18 public abstract void Delete(Depositor depositor); 19 20 //通知儲戶 21 public void Notify() 22 { 23 foreach (Depositor depositor in observers) 24 { 25 if (depositor.AccountIsChanged) 26 { 27 depositor.Update(depositor.Balance, depositor.OperationDateTime); 28 //賬戶發生了變化,並且通知了,儲戶的賬戶就認為沒有變化 29 depositor.AccountIsChanged = false; 30 } 31 } 32 } 33 } 34 35 //北京銀行短信系統,是被觀察者--該類型相當於具體主體角色ConcreteSubject 36 public sealed class BeiJingBankMessageSystem : BankMessageSystem 37 { 38 //增加預約儲戶 39 public override void Add(Depositor depositor) 40 { 41 //應該先判斷該用戶是否存在,存在不操作,不存在則增加到儲戶列表中,這里簡化了 42 observers.Add(depositor); 43 } 44 45 //刪除預約儲戶 46 public override void Delete(Depositor depositor) 47 { 48 //應該先判斷該用戶是否存在,存在則刪除,不存在無操作,這里簡化了 49 observers.Remove(depositor); 50 } 51 } 52 53 //儲戶的抽象接口--相當於抽象觀察者角色(Observer) 54 public abstract class Depositor 55 { 56 //狀態數據 57 private string _name; 58 private int _balance; 59 private int _total; 60 private bool _isChanged; 61 62 //初始化狀態數據 63 protected Depositor(string name, int total) 64 { 65 this._name = name; 66 this._balance = total;//存款總額等於余額 67 this._isChanged = false;//賬戶未發生變化 68 } 69 70 //儲戶的名稱,假設可以唯一區別的 71 public string Name 72 { 73 get { return _name; } 74 private set { this._name = value; } 75 } 76 77 public int Balance 78 { 79 get { return this._balance; } 80 } 81 82 //取錢 83 public void GetMoney(int num) 84 { 85 if (num <= this._balance && num > 0) 86 { 87 this._balance = this._balance - num; 88 this._isChanged = true; 89 OperationDateTime = DateTime.Now; 90 } 91 } 92 93 //賬戶操作時間 94 public DateTime OperationDateTime { get; set; } 95 96 //賬戶是否發生變化 97 public bool AccountIsChanged 98 { 99 get { return this._isChanged; } 100 set { this._isChanged = value; } 101 } 102 103 //更新儲戶狀態 104 public abstract void Update(int currentBalance, DateTime dateTime); 105 } 106 107 //北京的具體儲戶--相當於具體觀察者角色ConcreteObserver 108 public sealed class BeiJingDepositor : Depositor 109 { 110 public BeiJingDepositor(string name, int total) : base(name, total) { } 111 112 public override void Update(int currentBalance, DateTime dateTime) 113 { 114 Console.WriteLine(Name + ":賬戶發生了變化,變化時間是" + dateTime.ToString() + ",當前余額是" + currentBalance.ToString()); 115 } 116 } 117 118 119 // 客戶端(Client) 120 class Program 121 { 122 static void Main(string[] args) 123 { 124 //我們有了三位儲戶,都是武林高手,也比較有錢 125 Depositor huangFeiHong = new BeiJingDepositor("黃飛鴻", 3000); 126 Depositor fangShiYu = new BeiJingDepositor("方世玉", 1300); 127 Depositor hongXiGuan = new BeiJingDepositor("洪熙官", 2500); 128 129 BankMessageSystem beijingBank = new BeiJingBankMessageSystem(); 130 //這三位開始訂閱銀行短信業務 131 beijingBank.Add(huangFeiHong); 132 beijingBank.Add(fangShiYu); 133 beijingBank.Add(hongXiGuan); 134 135 //黃飛鴻取100塊錢 136 huangFeiHong.GetMoney(100); 137 beijingBank.Notify(); 138 139 //黃飛鴻和方世玉都取了錢 140 huangFeiHong.GetMoney(200); 141 fangShiYu.GetMoney(200); 142 beijingBank.Notify(); 143 144 //他們三個都取了錢 145 huangFeiHong.GetMoney(320); 146 fangShiYu.GetMoney(4330); 147 hongXiGuan.GetMoney(332); 148 beijingBank.Notify(); 149 150 Console.Read(); 151 } 152 } 153 }
觀察者模式有些麻煩的地方就是關於狀態的處理,我這里面涉及了一些狀態的處理,大家可以細細體會一下,模式還是要多多練習,多多寫,里面的道理就不難理解了。
三、觀察者模式的實現要點:
使用面向對象的抽象,Observer模式使得我們可以獨立地改變目標與觀察者(面向對象中的改變不是指改代碼,而是指擴展、子類化、實現接口),從而使二者之間的依賴關系達致松耦合。
目標發送通知時,無需指定觀察者,通知(可以攜帶通知信息作為參數)會自動傳播。觀察者自己決定是否需要訂閱通知,目標對象對此一無所知。
在C#的event中,委托充當了抽象的Observer接口,而提供事件的對象充當了目標對象。委托是比抽象Observer接口更為松耦合的設計。
3.1】、觀察者模式的優點:
(1)、觀察者模式實現了表示層和數據邏輯層的分離,並定義了穩定的更新消息傳遞機制,並抽象了更新接口,使得可以有各種各樣不同的表示層,即觀察者。
(2)、觀察者模式在被觀察者和觀察者之間建立了一個抽象的耦合,被觀察者並不知道任何一個具體的觀察者,只是保存着抽象觀察者的列表,每個具體觀察者都符合一個抽象觀察者的接口。
(3)、觀察者模式支持廣播通信。被觀察者會向所有的注冊過的觀察者發出通知。
3.2】、觀察者模式的缺點:
(1)、如果一個被觀察者有很多直接和間接的觀察者時,將所有的觀察者都通知到會花費很多時間。
(2)、雖然觀察者模式可以隨時使觀察者知道所觀察的對象發送了變化,但是觀察者模式沒有相應的機制使觀察者知道所觀察的對象是怎樣發生變化的。
(3)、如果在被觀察者之間有循環依賴的話,被觀察者會觸發它們之間進行循環調用,導致系統崩潰,在使用觀察者模式應特別注意這點。
四、.NET 中觀察者模式的實現
我上面寫了一點,“在C#的event中,委托充當了抽象的Observer接口,而提供事件的對象充當了目標對象。委托是比抽象Observer接口更為松耦合的設計。”,其實在Net里面實現的觀察者模式做了一些改變,用委托或者說是事件來實現觀察者模式。事件我們都很明白,我們可以注冊控件的事件,當觸發控件的動作時候,相應的事件就會執行,在事件的執行過程中我們就可以做相關的提醒業務。這里關於觀察者模式在Net里面的實現就不說了,如果大家不明白,可以多看看相關委托或者事件的相關資料。
五、總結
終於寫完了,這個模式主要是花在了代碼的書寫上。因為我寫每篇文章的時候,模式實現代碼都是當時現想的,要組織代碼關系,讓其更合理,所以時間就花了不少,但是是理解更好了。該模式不是很難,結構也不是很復雜,唯一讓我們多多注意的是狀態的管理。這個模式結合實例理解是很容易的,模式的使用我們不能照搬,要理解,當然多多的聯系和寫代碼也是必不可少的,我們使用模式的一貫宗旨是通過重構和迭代,在我們的代碼中實現相應的模式。