.NET设计模式(19):观察者模式(Observer Pattern)
概述
在軟件構(gòu)建過程中,我們需要為某些對(duì)象建立一種“通知依賴關(guān)系” ——一個(gè)對(duì)象(目標(biāo)對(duì)象)的狀態(tài)發(fā)生改變,所有的依賴對(duì)象(觀察者對(duì)象)都將得到通知。如果這樣的依賴關(guān)系過于緊密,將使軟件不能很好地抵御變化。使用面向?qū)ο蠹夹g(shù),可以將這種依賴關(guān)系弱化,并形成一種穩(wěn)定的依賴關(guān)系。從而實(shí)現(xiàn)軟件體系結(jié)構(gòu)的松耦合。
意圖
定義對(duì)象間的一種一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí), 所有依賴于它的對(duì)象都得到通知并被自動(dòng)更新。[GOF 《設(shè)計(jì)模式》]
結(jié)構(gòu)圖
圖1 Observer模式結(jié)構(gòu)圖
生活中的例子
觀察者定義了對(duì)象間一對(duì)多的關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)變化時(shí),所有依賴它的對(duì)象都得到通知并且自動(dòng)地更新。拍賣演示了這種模式。每個(gè)投標(biāo)人都有一個(gè)標(biāo)有數(shù)字的牌子用于出價(jià)。拍賣師開始拍賣時(shí),他觀察是否有牌子舉起出價(jià)。每次接受一個(gè)新的出價(jià)都改變了拍賣的當(dāng)前價(jià)格,并且廣播給所有的投標(biāo)人進(jìn)行新的出價(jià)。
圖2 使用拍賣例子的觀察者模式
Observer模式解說
下面通過一個(gè)例子來說明Observer模式。監(jiān)控某一個(gè)公司的股票價(jià)格變化,可以有多種方式,通知的對(duì)象可以是投資者,或者是發(fā)送到移動(dòng)設(shè)備,還有電子郵件等。一開始我們先不考慮Observer模式,通過一步步地重構(gòu),最終重構(gòu)為Observer模式。現(xiàn)在有這樣兩個(gè)類:Microsoft和Investor,如下圖所示:
圖3 UML靜態(tài)圖示例
它們的實(shí)現(xiàn)如下:
public?class?Microsoft
{
????private?Investor?_investor;
????private?String?_symbol;
????private?double?_price;
????public?void?Update()
????{
????????_investor.SendData(this);
????}
????public?Investor?Investor
????{
????????get?{?return?_investor;?}
????????set?{?_investor?=?value;?}
????}
????public?String?Symbol
????{
????????get?{?return?_symbol;?}
????????set?{?_symbol?=?value;?}
????}
????public?double?Price
????{
????????get?{?return?_price;?}
????????set?{?_price?=?value;?}
????}
}
public?class?Investor
{
????private?string?_name;
????public?Investor(string?name)
????{
????????this._name?=?name;
????}
????public?void?SendData(Microsoft?ms)
????{
????????Console.WriteLine("Notified?{0}?of?{1}'s?"?+?"change?to?{2:C}",?_name,?ms.Symbol,ms.Price);
????}
}
簡(jiǎn)單的客戶端實(shí)現(xiàn):
class?Program{
????static?void?Main(string[]?args)
????{
????????Investor?investor?=?new?Investor("Jom");
????????Microsoft?ms?=?new?Microsoft();
????????ms.Investor?=?investor;
????????ms.Symbol?=?"Microsoft";
????????ms.Price?=?120.00;
????????ms.Update();
????????Console.ReadLine();
????}
}
運(yùn)行后結(jié)果如下:
Notified Jom of Microsoft's change to ¥120
可以看到,這段代碼運(yùn)行并沒有問題,也確實(shí)實(shí)現(xiàn)了我們最初的設(shè)想的功能,把Microsoft的股票價(jià)格變化通知到了Jom投資者那兒。但是這里面出現(xiàn)了如下幾個(gè)問題:
1.Microsoft和Investor之間形成了一種雙向的依賴關(guān)系,即Microsoft調(diào)用了Investor的方法,而Investor調(diào)用了Microsoft類的屬性。如果有其中一個(gè)類變化,有可能會(huì)引起另一個(gè)的變化。
2.當(dāng)出現(xiàn)一種的通知對(duì)象,比如說是移動(dòng)設(shè)備Mobile:
public?class?Mobile{
????private?string?_no;
????public?Mobile(string?No)
????{
????????this._no?=?No;
????}
????public?void?SendData(Microsoft?ms)
????{
????????Console.WriteLine("Notified?{0}?of?{1}'s?"?+?"change?to?{2:C}",?_no,?ms.Symbol,?ms.Price);
????}
}
這時(shí)候?qū)?yīng)的Microsoft的類就應(yīng)該改變?yōu)槿缦麓a,在Microsot類中增加Mobile,同時(shí)修改Update()方法使其可以通知到移動(dòng)設(shè)備:
public?class?Microsoft{
????private?Investor?_investor;
????private?Mobile?_mobile;
????private?String?_symbol;
????private?double?_price;
?
????public?void?Update()
????{
????????_investor.SendData(this);
????????_mobile.SendData(this);
????}
?
????public?Mobile?Mobile
????{
????????get?{?return?_mobile;?}
????????set?{?_mobile?=?value;?}
????}
????public?Investor?Investor
????{
????????get?{?return?_investor;?}
????????set?{?_investor?=?value;?}
????}?
????public?String?Symbol
????{
????????get?{?return?_symbol;?}
????????set?{?_symbol?=?value;?}
????}
????public?double?Price
????{
????????get?{?return?_price;?}
????????set?{?_price?=?value;?}
????}
}
顯然這樣的設(shè)計(jì)極大的違背了“開放-封閉”原則,這不是我們所想要的,僅僅是新增加了一種通知對(duì)象,就需要對(duì)原有的Microsoft類進(jìn)行修改,這樣的設(shè)計(jì)是很糟糕的。對(duì)此做進(jìn)一步的抽象,既然出現(xiàn)了多個(gè)通知對(duì)象,我們就為這些對(duì)象之間抽象出一個(gè)接口,用它來取消Microsoft和具體的通知對(duì)象之間依賴。
圖4 靜態(tài)UML圖示例
實(shí)現(xiàn)代碼如下:
public?interface?IObserver{
????void?SendData(Microsoft?ms);
}
?
public?class?Investor?:?IObserver
{
????private?string?_name;
????public?Investor(string?name)
????{
????????this._name?=?name;
????}
????public?void?SendData(Microsoft?ms)
????{
????????Console.WriteLine("Notified?{0}?of?{1}'s?"?+?"change?to?{2:C}",?_name,?ms.Symbol,ms.Price);
????}
}
public?class?Microsoft
{
????private?IObserver?_investor;
????private?String?_symbol;
????private?double?_price;
????public?void?Update()
????{
????????_investor.SendData(this);
????}
?
????public?String?Symbol
????{
????????get?{?return?_symbol;?}
????????set?{?_symbol?=?value;?}
????}
????public?double?Price
????{
????????get?{?return?_price;?}
????????set?{?_price?=?value;?}
????}
????public?IObserver?Investor
????{
????????get?{?return?_investor;?}
????????set?{?_investor?=?value;?}
????}
}
做到這一步,可以看到,我們?cè)诮档蛢烧叩囊蕾囆陨弦呀?jīng)邁進(jìn)了一小步,正在朝著弱依賴性這個(gè)方向變化。在Microsoft類中已經(jīng)不再依賴于具體的Investor,而是依賴于接口IObserver。
但同時(shí)我們看到,再新出現(xiàn)一個(gè)移動(dòng)設(shè)備這樣的通知對(duì)象,Microsoft類仍然需要改變,對(duì)此我們?cè)僮鋈缦轮貥?gòu),在Microsoft中維護(hù)一個(gè)IObserver列表,同時(shí)提供相應(yīng)的維護(hù)方法。
圖5 靜態(tài)UML示例圖
Microsoft類的實(shí)現(xiàn)代碼如下:
public?class?Microsoft{
????private?List<IObserver>?observers?=?new?List<IObserver>();
????private?String?_symbol;
????private?double?_price;
????public?void?Update()
????{
????????foreach?(IObserver?ob?in?observers)
????????{
????????????ob.SendData(this);
????????}
????}
????public?void?AddObserver(IObserver?observer)
????{
????????observers.Add(observer);
????}
????public?void?RemoveObserver(IObserver?observer)
????{
????????observers.Remove(observer);
????}
????public?String?Symbol
????{
????????get?{?return?_symbol;?}
????????set?{?_symbol?=?value;?}
????}
????public?double?Price
????{
????????get?{?return?_price;?}
????????set?{?_price?=?value;?}
????}
}
此時(shí)客戶端的調(diào)用代碼:
class?Program{
????static?void?Main(string[]?args)
????{
????????IObserver?investor1?=?new?Investor("Jom");
????????IObserver?investor2?=?new?Investor("TerryLee");
????????Microsoft?ms?=?new?Microsoft();
????????ms.Symbol?=?"Microsoft";
????????ms.Price?=?120.00;
????????ms.AddObserver(investor1);
????????ms.AddObserver(investor2);
????????ms.Update();
????????Console.ReadLine();
????}
}
走到這一步,已經(jīng)有了Observer模式的影子了,Microsoft類不再依賴于具體的Investor,而是依賴于抽象的IOberver。存在著的一個(gè)問題是Investor仍然依賴于具體的公司Microsoft,況且公司還會(huì)有很多IBM,Google等,解決這樣的問題很簡(jiǎn)單,只需要再對(duì)Microsoft類做一次抽象。如下圖所示:
圖6 靜態(tài)UML示例圖
實(shí)現(xiàn)代碼如下:
public?abstract?class?Stock{
????private?List<IObserver>?observers?=?new?List<IObserver>();
????private?String?_symbol;
????private?double?_price;
????public?Stock(String?symbol,?double?price)
????{
????????this._symbol?=?symbol;
????????this._price?=?price;
????}
????public?void?Update()
????{
????????foreach?(IObserver?ob?in?observers)
????????{
????????????ob.SendData(this);
????????}
????}
????public?void?AddObserver(IObserver?observer)
????{
????????observers.Add(observer);
????}
????public?void?RemoveObserver(IObserver?observer)
????{
????????observers.Remove(observer);
????}
????public?String?Symbol
????{
????????get?{?return?_symbol;?}
????}
????public?double?Price
????{
????????get?{?return?_price;?}
????}
}
public?class?Microsoft?:?Stock
{
????public?Microsoft(String?symbol,?double?price)
????????:?base(symbol,?price)
????{?}
}
public?interface?IObserver
{
????void?SendData(Stock?stock);
}
public?class?Investor?:?IObserver
{
????private?string?_name;
????public?Investor(string?name)
????{
????????this._name?=?name;
????}
????public?void?SendData(Stock?stock)
????{
????????Console.WriteLine("Notified?{0}?of?{1}'s?"?+?"change?to?{2:C}",?_name,?stock.Symbol,stock.Price);
????}
}
客戶端程序代碼如下:
class?Program{
????static?void?Main(string[]?args)
????{
????????Stock?ms?=?new?Microsoft("Microsoft",120.00);
????????ms.AddObserver(new?Investor("Jom"));
????????ms.AddObserver(new?Investor("TerryLee"));
????????ms.Update();
????????Console.ReadLine();
????}
}
到這里我們可以看到,通過不斷的重構(gòu),不斷地抽象,我們由一開始的很糟糕的設(shè)計(jì),逐漸重構(gòu)為使用Observer模式的這樣一個(gè)方案。在這個(gè)例子里面,IOberser充當(dāng)了觀察者的角色,而Stock則扮演了主題對(duì)象角色,在任何時(shí)候,只要調(diào)用了Stock的Update()方法,它就會(huì)通知它的所有觀察者對(duì)象。同時(shí)可以看到,通過Observer模式,取消了直接依賴,變?yōu)殚g接依賴,這樣大大提供了系統(tǒng)的可維護(hù)性和可擴(kuò)展性。
推模式與拉模式
對(duì)于發(fā)布-訂閱模型,大家都很容易能想到推模式與拉模式,用SQL Server做過數(shù)據(jù)庫(kù)復(fù)制的朋友對(duì)這一點(diǎn)很清楚。在Observer模式中同樣區(qū)分推模式和拉模式,我先簡(jiǎn)單的解釋一下兩者的區(qū)別:推模式是當(dāng)有消息時(shí),把消息信息以參數(shù)的形式傳遞(推)給所有觀察者,而拉模式是當(dāng)有消息時(shí),通知消息的方法本身并不帶任何的參數(shù),是由觀察者自己到主體對(duì)象那兒取回(拉)消息。知道了這一點(diǎn),大家可能很容易發(fā)現(xiàn)上面我所舉的例子其實(shí)是一種推模式的Observer模式。我們先看看這種模式帶來了什么好處:當(dāng)有消息時(shí),所有的觀察者都會(huì)直接得到全部的消息,并進(jìn)行相應(yīng)的處理程序,與主體對(duì)象沒什么關(guān)系,兩者之間的關(guān)系是一種松散耦合。但是它也有缺陷,第一是所有的觀察者得到的消息是一樣的,也許有些信息對(duì)某個(gè)觀察者來說根本就用不上,也就是觀察者不能“按需所取”;第二,當(dāng)通知消息的參數(shù)有變化時(shí),所有的觀察者對(duì)象都要變化。鑒于以上問題,拉模式就應(yīng)運(yùn)而生了,它是由觀察者自己主動(dòng)去取消息,需要什么信息,就可以取什么,不會(huì)像推模式那樣得到所有的消息參數(shù)。OK,說到這兒,你是否對(duì)于推模式和拉模式有了一點(diǎn)了解呢?我把前面的例子修改為了拉模式,供大家參考,可以看到通知方法是沒有任何參數(shù)的:
public?abstract?class?Stock{
????private?List<IObserver>?observers?=?new?List<IObserver>();
????private?String?_symbol;
????private?double?_price;
????public?Stock(String?symbol,?double?price)
????{
????????this._symbol?=?symbol;
????????this._price?=?price;
????}
????public?void?Update()
????{
????????foreach?(IObserver?ob?in?observers)
????????{
????????????ob.SendData();
????????}
????}
????public?void?AddObserver(IObserver?observer)
????{
????????observers.Add(observer);
????}
????public?void?RemoveObserver(IObserver?observer)
????{
????????observers.Remove(observer);
????}
????public?String?Symbol
????{
????????get?{?return?_symbol;?}
????}
????public?double?Price
????{
????????get?{?return?_price;?}
????}
}
public?class?Microsoft?:?Stock
{
????public?Microsoft(String?symbol,?double?price)
????????:?base(symbol,?price)
????{?}
}
public?interface?IObserver
{
????void?SendData();
}
public?class?Investor?:?IObserver
{
????private?string?_name;
????private?Stock?_stock;
????public?Investor(string?name,Stock?stock)
????{
????????this._name?=?name;
????????this._stock?=?stock;
????}
????public?void?SendData()
????{
????????Console.WriteLine("Notified?{0}?of?{1}'s?"?+?"change?to?{2:C}",?_name,?_stock.Symbol,?_stock.Price);
????}
}
class?Program
{
????static?void?Main(string[]?args)
????{
????????Stock?ms?=?new?Microsoft("Microsoft",?120.00);
????????ms.AddObserver(new?Investor("Jom",ms));
????????ms.AddObserver(new?Investor("TerryLee",ms));
????????ms.Update();
????????Console.ReadLine();
????}
}
當(dāng)然拉模式也是有一些缺點(diǎn)的,主體對(duì)象和觀察者之間的耦合加強(qiáng)了,但是這可以通過抽象的手段使這種耦合關(guān)系減到最小。[感謝idior的意見]
.NET中的Observer模式
在.NET中,相信大家對(duì)于事件和委托都已經(jīng)不陌生了,這里就不具體多說了。利用事件和委托來實(shí)現(xiàn)Observer模式我認(rèn)為更加的簡(jiǎn)單和優(yōu)雅,也是一種更好的解決方案。因?yàn)樵谏厦娴氖纠形覀兛梢钥吹?#xff0c;雖然取消了直接耦合,但是又引入了不必要的約束(暫且這么說吧)。即那些子類必須都繼承于主題父類,還有觀察者接口等。網(wǎng)上有很多這方面的例子,上面的例子簡(jiǎn)單的用事件和委托實(shí)現(xiàn)如下,僅供大家參考:
{
????static?void?Main(string[]?args)
????{
????????Stock?stock?=?new?Stock("Microsoft",?120.00);
????????Investor?investor?=?new?Investor("Jom");
????????stock.NotifyEvent?+=?new?NotifyEventHandler(investor.SendData);
????????stock.Update();
????????Console.ReadLine();
????}
}
public?delegate?void?NotifyEventHandler(object?sender);
public?class?Stock
{
????public?NotifyEventHandler?NotifyEvent;
????private?String?_symbol;
????private?double?_price;
????public?Stock(String?symbol,?double?price)
????{
????????this._symbol?=?symbol;
????????this._price?=?price;
????}
????public?void?Update()
????{
????????OnNotifyChange();????
????}
????public?void?OnNotifyChange()
????{
????????if?(NotifyEvent?!=?null)
????????{
????????????NotifyEvent(this);
????????}
????}
????public?String?Symbol
????{
????????get?{?return?_symbol;?}
????}
????public?double?Price
????{
????????get?{?return?_price;?}
????}
}
?
public?class?Investor
{
????private?string?_name;
????public?Investor(string?name)
????{
????????this._name?=?name;
????}
????public?void?SendData(object?obj)
????{
????????if?(obj?is?Stock)
????????{
????????????Stock?stock?=?(Stock)obj;
????????????Console.WriteLine("Notified?{0}?of?{1}'s?"?+?"change?to?{2:C}",?_name,?stock.Symbol,?stock.Price);
????????}
????}
}
效果及實(shí)現(xiàn)要點(diǎn)
1.使用面向?qū)ο蟮某橄?#xff0c;Observer模式使得我們可以獨(dú)立地改變目標(biāo)與觀察者,從而使二者之間的依賴關(guān)系達(dá)到松耦合。
2.目標(biāo)發(fā)送通知時(shí),無需指定觀察者,通知(可以攜帶通知信息作為參數(shù))會(huì)自動(dòng)傳播。觀察者自己決定是否需要訂閱通知。目標(biāo)對(duì)象對(duì)此一無所知。
3.在C#中的Event。委托充當(dāng)了抽象的Observer接口,而提供事件的對(duì)象充當(dāng)了目標(biāo)對(duì)象,委托是比抽象Observer接口更為松耦合的設(shè)計(jì)。
適用性
1.當(dāng)一個(gè)抽象模型有兩個(gè)方面, 其中一個(gè)方面依賴于另一方面。將這二者封裝在獨(dú)立的對(duì)象中以使它們可以各自獨(dú)立地改變和復(fù)用。
2.當(dāng)對(duì)一個(gè)對(duì)象的改變需要同時(shí)改變其它對(duì)象, 而不知道具體有多少對(duì)象有待改變。
3.當(dāng)一個(gè)對(duì)象必須通知其它對(duì)象,而它又不能假定其它對(duì)象是誰。換言之, 你不希望這些對(duì)象是緊密耦合的。
總結(jié)
通過Observer模式,把一對(duì)多對(duì)象之間的通知依賴關(guān)系的變得更為松散,大大地提高了程序的可維護(hù)性和可擴(kuò)展性,也很好的符合了開放-封閉原則。
參考資料
Erich Gamma等,《設(shè)計(jì)模式:可復(fù)用面向?qū)ο筌浖幕A(chǔ)》,機(jī)械工業(yè)出版社
Robert C.Martin,《敏捷軟件開發(fā):原則、模式與實(shí)踐》,清華大學(xué)出版社
閻宏,《Java與模式》,電子工業(yè)出版社
Alan Shalloway James R. Trott,《Design Patterns Explained》,中國(guó)電力出版社
MSDN WebCast 《C#面向?qū)ο笤O(shè)計(jì)模式縱橫談(19):Observer 觀察者模式(行為型模式)》
更多設(shè)計(jì)模式文章請(qǐng)?jiān)L問.NET設(shè)計(jì)模式系列
轉(zhuǎn)載于:https://www.cnblogs.com/Aioria0622/archive/2007/11/21/966387.html
總結(jié)
以上是生活随笔為你收集整理的.NET设计模式(19):观察者模式(Observer Pattern)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 07/11/20 资料整理
- 下一篇: 神不知鬼不觉地置换 XP用户密码