一、 觀察者(Observer)模式
觀察者模式又叫做發(fā)布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-******(Source/Listener)模式或從屬者(Dependents)模式。
觀察者模式定義了一種一對多的依賴關(guān)系,讓多個觀察者對象同時監(jiān)聽某一個主題對象。這個主題對象在狀態(tài)上發(fā)生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。
一個軟件系統(tǒng)常常要求在某一個對象的狀態(tài)發(fā)生變化的時候,某些其它的對象做出相應(yīng)的改變。做到這一點的設(shè)計方案有很多,但是為了使系統(tǒng)能夠易于復(fù)
用,應(yīng)該選擇低耦合度的設(shè)計方案。減少對象之間的耦合有利于系統(tǒng)的復(fù)用,但是同時設(shè)計師需要使這些低耦合度的對象之間能夠維持行動的協(xié)調(diào)一致,保證高度的
協(xié)作(Collaboration)。觀察者模式是滿足這一要求的各種設(shè)計方案中最重要的一種。
二、 觀察者模式的結(jié)構(gòu)
觀察者模式的類圖如下:

可以看出,在這個觀察者模式的實現(xiàn)里有下面這些角色:
- 抽象主題(Subject)角色:主題角色把所有對觀察考對象的引用保存在一個聚集里,每個主題都可以有任何數(shù)量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象,主題角色又叫做抽象被觀察者(Observable)角色,一般用一個抽象類或者一個接口實現(xiàn)。
- 抽象觀察者(Observer)角色:為所有的具體觀察者定義一個接口,在得到主題的通知時更新自己。這個接口叫做更新接口。抽象觀察者角色一般用一個抽象類或者一個接口實現(xiàn)。在這個示意性的實現(xiàn)中,更新接口只包含一個方法(即Update()方法),這個方法叫做更新方法。
- 具體主題(ConcreteSubject)角色:將有關(guān)狀態(tài)存入具體現(xiàn)察者對象;在具體主題的內(nèi)部狀態(tài)改變時,給所有登記過的觀察者發(fā)出通知。具體主題角色又叫做具體被觀察者角色(Concrete Observable)。具體主題角色通常用一個具體子類實現(xiàn)。
- 具體觀察者(ConcreteObserver)角色:存儲與主題的狀態(tài)自恰的狀態(tài)。具體現(xiàn)察者角色實現(xiàn)抽象觀察者角色所要求的更新接口,以便使本身的狀態(tài)與主題的狀態(tài)相協(xié)調(diào)。如果需要,具體現(xiàn)察者角色可以保存一個指向具體主題對象的引用。具體觀察者角色通常用一個具體子類實現(xiàn)。
從具體主題角色指向抽象觀察者角色的合成關(guān)系,代表具體主題對象可以有任意多個對抽象觀察者?韻蟮囊謾V允褂貿(mào)橄蠊鄄煺叨皇薔嚀騫
鄄煺擼馕蹲胖魈舛韻蟛恍枰酪昧四男〤oncreteObserver類型,而只知道抽象Observer類型。這就使得具體主題對象可以動態(tài)地維
護一系列的對觀察者對象的引用,并在需要的時候調(diào)用每一個觀察者共有的Update()方法。這種做法叫做"針對抽象編程"。
三、 觀察者模式的示意性源代碼
// Observer pattern -- Structural example
using System;
using System.Collections;

// "Subject"
abstract class Subject
  {
// Fields
private ArrayList observers = new ArrayList();

// Methods
public void Attach( Observer observer )
 {
observers.Add( observer );
}

public void Detach( Observer observer )
 {
observers.Remove( observer );
}

public void Notify()
 {
foreach( Observer o in observers )
o.Update();
}
}

// "ConcreteSubject"
class ConcreteSubject : Subject
  {
// Fields
private string subjectState;

// Properties
public string SubjectState
 {
 get { return subjectState; }
 set { subjectState = value; }
}
}

// "Observer"
abstract class Observer
  {
// Methods
abstract public void Update();
}

// "ConcreteObserver"
class ConcreteObserver : Observer
  {
// Fields
private string name;
private string observerState;
private ConcreteSubject subject;

// Constructors
public ConcreteObserver( ConcreteSubject subject,
string name )
 {
this.subject = subject;
this.name = name;
}

// Methods
override public void Update()
 {
observerState = subject.SubjectState;
Console.WriteLine( "Observer {0}‘s new state is {1}",
name, observerState );
}

// Properties
public ConcreteSubject Subject
 {
 get { return subject; }
 set { subject = value; }
}
}

 /**//// <summary>
/// Client test
/// </summary> public class Client
  {
public static void Main( string[] args )
 {
// Configure Observer structure
ConcreteSubject s = new ConcreteSubject();
s.Attach( new ConcreteObserver( s, "1" ) );
s.Attach( new ConcreteObserver( s, "2" ) );
s.Attach( new ConcreteObserver( s, "3" ) );

// Change subject and notify observers
s.SubjectState = "ABC";
s.Notify();
}
}
四、 C#中的Delegate與Event
實際上在C#中實現(xiàn)Observer模式?jīng)]有這么辛苦,.NET中提供了Delegate與Event機制,我們可以利用這種機制簡化Observer模式。關(guān)于Delegate與Event的使用方法請參考相關(guān)文檔。改進后的Observer模式實現(xiàn)如下:
// Observer pattern -- Structural example
using System;

//Delegate
delegate void UpdateDelegate();

//Subject
class Subject
  {
public event UpdateDelegate UpdateHandler;
// Methods
public void Attach( UpdateDelegate ud )
 {
UpdateHandler += ud;
}

public void Detach( UpdateDelegate ud )
 {
UpdateHandler -= ud;
}
public void Notify()
 {
if(UpdateHandler != null) UpdateHandler();
}

}

//ConcreteSubject
class ConcreteSubject : Subject
  {
// Fields
private string subjectState;

// Properties
public string SubjectState
 {
 get { return subjectState; }
 set { subjectState = value; }
}
}

// "ConcreteObserver"
class ConcreteObserver
  {
// Fields
private string name;
private string observerState;
private ConcreteSubject subject;

// Constructors
public ConcreteObserver( ConcreteSubject subject,
string name )
 {
this.subject = subject;
this.name = name;
}

// Methods
public void Update()
 {
observerState = subject.SubjectState;
Console.WriteLine( "Observer {0}‘s new state is {1}",
name, observerState );
}

// Properties
public ConcreteSubject Subject
 {
 get { return subject; }
 set { subject = value; }
}
}

// "ConcreteObserver"
class AnotherObserver
  {
// Methods
public void Show()
 {
Console.WriteLine("AnotherObserver got an Notification!");
}
}

public class Client
  {
public static void Main(string[] args)
 {
ConcreteSubject s = new ConcreteSubject();
ConcreteObserver o1 = new ConcreteObserver(s, "1");
ConcreteObserver o2 = new ConcreteObserver(s, "2");
AnotherObserver o3 = new AnotherObserver();
s.Attach(new UpdateDelegate(o1.Update));
s.Attach(new UpdateDelegate(o2.Update));
s.Attach(new UpdateDelegate(o3.Show));

s.SubjectState = "ABC";
s.Notify();

Console.WriteLine("--------------------------");
s.Detach(new UpdateDelegate(o1.Update));

s.SubjectState = "DEF";
s.Notify();
}
}
其中,關(guān)鍵的代碼如下:
delegate void UpdateDelegate();
定義一個Delegate,用來規(guī)范函數(shù)結(jié)構(gòu)。不管是ConcreteObserver類的Update方法還是AnotherObserver類
的Show方法都符合該Delegate。這不象用Observer接口來規(guī)范必須使用Update方法那么嚴(yán)格。只要符合Delegate所指定的方法
結(jié)構(gòu)的方法都可以在后面被事件所處理。
public event UpdateDelegate UpdateHandler;
定義一個事件,一旦觸發(fā),可以調(diào)用一組符合UpdateDelegate規(guī)范的方法。
public void Attach( UpdateDelegate ud )
 {
UpdateHandler += ud;
}
訂閱事件。只要是一個滿足UpdateDelegate的方法,就可以進行訂閱操作(如下所示)。
s.Attach(new UpdateDelegate(o1.Update));
s.Attach(new UpdateDelegate(o2.Update));
s.Attach(new UpdateDelegate(o3.Show));
在Notify方法中:
public void Notify()
 {
if(UpdateHandler != null) UpdateHandler();
}
只要UpdateHandler != null(表示有訂閱者),就可以觸發(fā)事件(UpdateHandler()),所有的訂閱者便會接到通知。
五、 一個實際應(yīng)用觀察者模式的例子
該例子演示了注冊的投資者在股票市場發(fā)生變化時,可以自動得到通知。該例子仍然使用的是傳統(tǒng)的Observer處理手段,至于如何轉(zhuǎn)換成Delegate與Event留給讀者自己考慮。
// Observer pattern -- Real World example
using System;
using System.Collections;

// "Subject"
abstract class Stock
  {
// Fields
protected string symbol;
protected double price;
private ArrayList investors = new ArrayList();

// Constructor
public Stock( string symbol, double price )
 {
this.symbol = symbol;
this.price = price;
}

// Methods
public void Attach( Investor investor )
 {
investors.Add( investor );
}

public void Detach( Investor investor )
 {
investors.Remove( investor );
}

public void Notify()
 {
foreach( Investor i in investors )
i.Update( this );
}

// Properties
public double Price
 {
 get { return price; }
set
 {
price = value;
Notify();
}
}

public string Symbol
 {
 get { return symbol; }
 set { symbol = value; }
}
}

// "ConcreteSubject"
class IBM : Stock
  {
// Constructor
public IBM( string symbol, double price )
 : base( symbol, price ) {}
}

// "Observer"
interface IInvestor
  {
// Methods
void Update( Stock stock );
}

// "ConcreteObserver"
class Investor : IInvestor
  {
// Fields
private string name;
private string observerState;
private Stock stock;

// Constructors
public Investor( string name )
 {
this.name = name;
}

// Methods
public void Update( Stock stock )
 {
Console.WriteLine( "Notified investor {0} of {1}‘s change to {2:C}",
name, stock.Symbol, stock.Price );
}

// Properties
public Stock Stock
 {
 get { return stock; }
 set { stock = value; }
}
}

 /**//// <summary>
/// ObserverApp test
/// </summary> public class ObserverApp
  {
public static void Main( string[] args )
 {
// Create investors
Investor s = new Investor( "Sorros" );
Investor b = new Investor( "Berkshire" );

// Create IBM stock and attach investors
IBM ibm = new IBM( "IBM", 120.00 );
ibm.Attach( s );
ibm.Attach( b );

// Change price, which notifies investors
ibm.Price = 120.10;
ibm.Price = 121.00;
ibm.Price = 120.50;
ibm.Price = 120.75;
}
}
六、 觀察者模式的優(yōu)缺點
Observer模式的優(yōu)點是實現(xiàn)了表示層和數(shù)據(jù)邏輯層的分離,并定義了穩(wěn)定的更新消息傳遞機制,類別清晰,并抽象了更新接口,使得可以有各種各樣不同的表示層(觀察者)。
但是其缺點是每個外觀對象必須繼承這個抽像出來的接口類,這樣就造成了一些不方便,比如有一個別人寫的外觀對象,并沒有繼承該抽象類,或者接口不
對,我們又希望不修改該類直接使用它。雖然可以再應(yīng)用Adapter模式來一定程度上解決這個問題,但是會造成更加復(fù)雜煩瑣的設(shè)計,增加出錯幾率。
觀察者模式的效果有以下幾個優(yōu)點:
(1)觀察者模式在被觀察者和觀察者之間建立一個抽象的耦合。被觀察者角色所知道的只是一個具體現(xiàn)察者聚集,每一個具體現(xiàn)察者都符合一個抽象觀察者
的接口。被觀察者并不認(rèn)識任何一個具體觀察者,它只知道它們都有一個共同的接口。由于被觀察者和觀察者沒有緊密地耦合在一起,因此它們可以屬于不同的抽象
化層次。
(2)觀察者模式支持廣播通信。被觀察者會向所有的登記過的觀察者發(fā)出通知。
觀察者模式有下面的一些缺點:
(1)如果一個被觀察者對象有很多直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
(2)如果在被觀察者之間有循環(huán)依賴的話,被觀察者會觸發(fā)它們之間進行循環(huán)調(diào)用,導(dǎo)致系統(tǒng)崩潰。在使用觀察考模式時要特別注意這一點。
(3)如果對觀察者的通知是通過另外的線程進行異步投遞的話,系統(tǒng)必須保證投遞是以自恰的方式進行的。
(4)雖然觀察者模式可以隨時使觀察者知道所觀察的對象發(fā)生了變化,但是觀察者模式?jīng)]有相應(yīng)的機制使觀察者知道所觀察的對象是怎么發(fā)生變化的。
|