.Net行為型設(shè)計(jì)模式之觀察者模式(Observer)
一、動(dòng)機(jī)(Motivate)
“觀察者模式”在現(xiàn)實(shí)生活中,實(shí)例其實(shí)是很多的,比如:八九十年代我們訂閱的報(bào)紙,我們會(huì)定期收到報(bào)紙,因?yàn)槲覀冇嗛喠?。銀行可以給儲(chǔ)戶發(fā)手機(jī)短信,也是“觀察者模式”很好的使用的例子,因?yàn)槲覀冇嗛喠算y行的短信業(yè)務(wù),當(dāng)我們賬戶余額發(fā)生變化就會(huì)收到通知等。
在軟件構(gòu)建過程中,我們需要為某些對象建立一種“通知依賴關(guān)系”——一個(gè)對象(目標(biāo)對象)的狀態(tài)發(fā)生改變,所有的依賴對象(觀察者對象)都將得到通知。如果這樣的依賴關(guān)系過于緊密,將使軟件不能很好地抵御變化。
使用面向?qū)ο蠹夹g(shù),可以將這種依賴關(guān)系弱化,并形成一種穩(wěn)定的依賴關(guān)系。從而實(shí)現(xiàn)軟件體系結(jié)構(gòu)的松耦合。
二、意圖(Intent)
定義對象間的一種一對多的依賴關(guān)系,以便當(dāng)一個(gè)對象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對象都得到通知并自動(dòng)更新。 ——《設(shè)計(jì)模式》GoF
三、結(jié)構(gòu)圖
四、模式的組成
可以看出,在觀察者模式的結(jié)構(gòu)圖有以下角色:
(1)、抽象主題角色(Subject):抽象主題把所有觀察者對象的引用保存在一個(gè)列表中,并提供增加和刪除觀察者對象的操作,抽象主題角色又叫做抽象被觀察者角色,一般由抽象類或接口實(shí)現(xiàn)。
(2)、抽象觀察者角色(Observer):為所有具體觀察者定義一個(gè)接口,在得到主題通知時(shí)更新自己,一般由抽象類或接口實(shí)現(xiàn)。
(3)、具體主題角色(ConcreteSubject):實(shí)現(xiàn)抽象主題接口,具體主題角色又叫做具體被觀察者角色。
(4)、具體觀察者角色(ConcreteObserver):實(shí)現(xiàn)抽象觀察者角色所要求的接口,以便使自身狀態(tài)與主題的狀態(tài)相協(xié)調(diào)。
五、觀察者模式的代碼實(shí)現(xiàn)
觀察者模式在顯示生活中也有類似的例子,比如:我們訂閱銀行短信業(yè)務(wù),當(dāng)我們賬戶發(fā)生改變,我們就會(huì)收到相應(yīng)的短信。類似的還有微信訂閱號,今天我們就以銀行給我發(fā)送短信當(dāng)我們賬戶余額發(fā)生變化的時(shí)候?yàn)槔齺碇v講觀察者模式的實(shí)現(xiàn),很簡單,現(xiàn)實(shí)生活正例子也很多,理解起來也很容易。
// 客戶端(Client) static void Main(string[] args) { //我們有了三位儲(chǔ)戶,都是武林高手,也比較有錢 Depositor huangFeiHong = new BeiJingDepositor("黃飛鴻", 3000); Depositor fangShiYu = new BeiJingDepositor("方世玉", 1300); Depositor hongXiGuan = new BeiJingDepositor("洪熙官", 2500); BankMessageSystem beijingBank = new BeiJingBankMessageSystem(); //這三位開始訂閱銀行短信業(yè)務(wù) beijingBank.Add(huangFeiHong); beijingBank.Add(fangShiYu); beijingBank.Add(hongXiGuan); //黃飛鴻取100塊錢 huangFeiHong.GetMoney(100); beijingBank.Notify(); //黃飛鴻和方世玉都取了錢 huangFeiHong.GetMoney(200); fangShiYu.GetMoney(200); beijingBank.Notify(); //他們?nèi)齻€(gè)都取了錢 huangFeiHong.GetMoney(320); fangShiYu.GetMoney(4330); hongXiGuan.GetMoney(332); beijingBank.Notify(); } //銀行短信系統(tǒng)抽象接口,是被觀察者--該類型相當(dāng)于抽象主體角色Subject public abstract class BankMessageSystem { protected IList<Depositor> observers; //構(gòu)造函數(shù)初始化觀察者列表實(shí)例 protected BankMessageSystem() { observers = new List<Depositor>(); } //增加預(yù)約儲(chǔ)戶 public abstract void Add(Depositor depositor); //刪除預(yù)約儲(chǔ)戶 public abstract void Delete(Depositor depositor); //通知儲(chǔ)戶 public void Notify() { foreach (Depositor depositor in observers) { if (depositor.AccountIsChanged) { depositor.Update(depositor.Balance, depositor.OperationDateTime); //賬戶發(fā)生了變化,并且通知了,儲(chǔ)戶的賬戶就認(rèn)為沒有變化 depositor.AccountIsChanged = false; } } } } //北京銀行短信系統(tǒng),是被觀察者--該類型相當(dāng)于具體主體角色ConcreteSubject public sealed class BeiJingBankMessageSystem : BankMessageSystem { //增加預(yù)約儲(chǔ)戶 public override void Add(Depositor depositor) { //應(yīng)該先判斷該用戶是否存在,存在不操作,不存在則增加到儲(chǔ)戶列表中,這里簡化了 observers.Add(depositor); } //刪除預(yù)約儲(chǔ)戶 public override void Delete(Depositor depositor) { //應(yīng)該先判斷該用戶是否存在,存在則刪除,不存在無操作,這里簡化了 observers.Remove(depositor); } } //儲(chǔ)戶的抽象接口--相當(dāng)于抽象觀察者角色(Observer) public abstract class Depositor { //狀態(tài)數(shù)據(jù) private string _name; private int _balance; private int _total; private bool _isChanged; //初始化狀態(tài)數(shù)據(jù) protected Depositor(string name, int total) { this._name = name; this._balance = total;//存款總額等于余額 this._isChanged = false;//賬戶未發(fā)生變化 } //儲(chǔ)戶的名稱,假設(shè)可以唯一區(qū)別的 public string Name { get { return _name; } private set { this._name = value; } } public int Balance { get { return this._balance; } } //取錢 public void GetMoney(int num) { if (num <= this._balance && num > 0) { this._balance = this._balance - num; this._isChanged = true; OperationDateTime = DateTime.Now; } } //賬戶操作時(shí)間 public DateTime OperationDateTime { get; set; } //賬戶是否發(fā)生變化 public bool AccountIsChanged { get { return this._isChanged; } set { this._isChanged = value; } } //更新儲(chǔ)戶狀態(tài) public abstract void Update(int currentBalance, DateTime dateTime); } //北京的具體儲(chǔ)戶--相當(dāng)于具體觀察者角色ConcreteObserver public sealed class BeiJingDepositor : Depositor { public BeiJingDepositor(string name, int total) : base(name, total) { } public override void Update(int currentBalance, DateTime dateTime) { Console.WriteLine(Name + ":賬戶發(fā)生了變化,變化時(shí)間是" + dateTime.ToString() + ",當(dāng)前余額是" + currentBalance.ToString()); } }
觀察者模式有些麻煩的地方就是關(guān)于狀態(tài)的處理,我這里面涉及了一些狀態(tài)的處理,大家可以細(xì)細(xì)體會(huì)一下,模式還是要多多練習(xí),多多寫,里面的道理就不難理解了。
六、觀察者模式的實(shí)現(xiàn)要點(diǎn):
使用面向?qū)ο蟮某橄?,Observer模式使得我們可以獨(dú)立地改變目標(biāo)與觀察者(面向?qū)ο笾械母淖儾皇侵父拇a,而是指擴(kuò)展、子類化、實(shí)現(xiàn)接口),從而使二者之間的依賴關(guān)系達(dá)致松耦合。
目標(biāo)發(fā)送通知時(shí),無需指定觀察者,通知(可以攜帶通知信息作為參數(shù))會(huì)自動(dòng)傳播。觀察者自己決定是否需要訂閱通知,目標(biāo)對象對此一無所知。
在C#的event中,委托充當(dāng)了抽象的Observer接口,而提供事件的對象充當(dāng)了目標(biāo)對象。委托是比抽象Observer接口更為松耦合的設(shè)計(jì)。
1、觀察者模式的優(yōu)點(diǎn):
(1)、觀察者模式實(shí)現(xiàn)了表示層和數(shù)據(jù)邏輯層的分離,并定義了穩(wěn)定的更新消息傳遞機(jī)制,并抽象了更新接口,使得可以有各種各樣不同的表示層,即觀察者。
(2)、觀察者模式在被觀察者和觀察者之間建立了一個(gè)抽象的耦合,被觀察者并不知道任何一個(gè)具體的觀察者,只是保存著抽象觀察者的列表,每個(gè)具體觀察者都符合一個(gè)抽象觀察者的接口。
(3)、觀察者模式支持廣播通信。被觀察者會(huì)向所有的注冊過的觀察者發(fā)出通知。
2、觀察者模式的缺點(diǎn):
(1)、如果一個(gè)被觀察者有很多直接和間接的觀察者時(shí),將所有的觀察者都通知到會(huì)花費(fèi)很多時(shí)間。
(2)、雖然觀察者模式可以隨時(shí)使觀察者知道所觀察的對象發(fā)送了變化,但是觀察者模式?jīng)]有相應(yīng)的機(jī)制使觀察者知道所觀察的對象是怎樣發(fā)生變化的。
(3)、如果在被觀察者之間有循環(huán)依賴的話,被觀察者會(huì)觸發(fā)它們之間進(jìn)行循環(huán)調(diào)用,導(dǎo)致系統(tǒng)崩潰,在使用觀察者模式應(yīng)特別注意這點(diǎn)。
七、.NET 中觀察者模式的實(shí)現(xiàn)
我上面寫了一點(diǎn),“在C#的event中,委托充當(dāng)了抽象的Observer接口,而提供事件的對象充當(dāng)了目標(biāo)對象。委托是比抽象Observer接口更為松耦合的設(shè)計(jì)。”,其實(shí)在Net里面實(shí)現(xiàn)的觀察者模式做了一些改變,用委托或者說是事件來實(shí)現(xiàn)觀察者模式。事件我們都很明白,我們可以注冊控件的事件,當(dāng)觸發(fā)控件的動(dòng)作時(shí)候,相應(yīng)的事件就會(huì)執(zhí)行,在事件的執(zhí)行過程中我們就可以做相關(guān)的提醒業(yè)務(wù)。這里關(guān)于觀察者模式在Net里面的實(shí)現(xiàn)就不說了,如果大家不明白,可以多看看相關(guān)委托或者事件的相關(guān)資料。
到此這篇關(guān)于.Net行為型設(shè)計(jì)模式之觀察者模式(Observer)的文章就介紹到這了。希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- .Net行為型設(shè)計(jì)模式之備忘錄模式(Memento)
- .Net行為型設(shè)計(jì)模式之訪問者模式(Visitor)
- .Net行為型設(shè)計(jì)模式之職責(zé)鏈模式(Chain of Responsibility)
- .Net行為型設(shè)計(jì)模式之策略模式(Stragety)
- .Net行為型設(shè)計(jì)模式之狀態(tài)模式(State)
- .Net行為型設(shè)計(jì)模式之中介者模式(Mediator)
- .Net行為型設(shè)計(jì)模式之迭代器模式(Iterator)
- .Net行為型設(shè)計(jì)模式之命令模式(Command)
- .Net行為型設(shè)計(jì)模式之模板方法模式(Template?Method)
- .Net行為型設(shè)計(jì)模式之解釋器模式(Interpreter)
相關(guān)文章
一步步打造漂亮的新聞列表(無刷新分頁、內(nèi)容預(yù)覽)第一步
新聞列表是信息管理系統(tǒng)中最常見的,也是最簡單的,一些簡單的新聞列表就是一個(gè)table,然后里面循環(huán)寫入數(shù)據(jù)2010-07-07一步步打造漂亮的新聞列表(無刷新分頁、內(nèi)容預(yù)覽)第二步
由于我們僅僅是項(xiàng)目中的一個(gè)小部分,但也差不多按照以上的順序進(jìn)行開發(fā),這是一個(gè)良好的習(xí)慣。我們將概要設(shè)計(jì)和詳細(xì)設(shè)計(jì)放在一起。2010-07-07ASP.NET系統(tǒng)關(guān)鍵字及保留字列表整理
ASP.NET系統(tǒng)關(guān)鍵字及保留字列表,大家在寫程序的時(shí)候一定要避免使用,免得引起不需要的麻煩2012-10-10asp.net網(wǎng)絡(luò)數(shù)據(jù)庫開發(fā)實(shí)例精解 源文件
asp.net網(wǎng)絡(luò)數(shù)據(jù)庫開發(fā)實(shí)例精解 源文件...2006-09-09