設計模式(19)-Observer Pattern

 ------------http://www.cnblogs.com/zhenyulu/articles/73723.html

一、 觀察者(Observer)模式

觀察者模式又叫做發佈-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。

觀察者模式定義了一種一對多的依賴關係,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象在狀態上發生變化時,會通知所有觀察者對象,使它們能夠自動更新自己。

一個軟件系統常常要求在某一個對象的狀態發生變化的時候,某些其它的對象做出相應的改變。做到這一點的設計方案有很多,但是爲了使系統能夠易於複用,應該選擇低耦合度的設計方案。減少對象之間的耦合有利於系統的複用,但是同時設計師需要使這些低耦合度的對象之間能夠維持行動的協調一致,保證高度的協作(Collaboration)。觀察者模式是滿足這一要求的各種設計方案中最重要的一種。


二、 觀察者模式的結構

觀察者模式的類圖如下:

 PicX00105.gif

可以看出,在這個觀察者模式的實現裏有下面這些角色:

  • 抽象主題(Subject)角色:主題角色把所有對觀察考對象的引用保存在一個聚集裏,每個主題都可以有任何數量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者對象,主題角色又叫做抽象被觀察者(Observable)角色,一般用一個抽象類或者一個接口實現。
  • 抽象觀察者(Observer)角色:爲所有的具體觀察者定義一個接口,在得到主題的通知時更新自己。這個接口叫做更新接口。抽象觀察者角色一般用一個抽象類或者一個接口實現。在這個示意性的實現中,更新接口只包含一個方法(即Update()方法),這個方法叫做更新方法。
  • 具體主題(ConcreteSubject)角色:將有關狀態存入具體現察者對象;在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色又叫做具體被觀察者角色(Concrete Observable)。具體主題角色通常用一個具體子類實現。
  • 具體觀察者(ConcreteObserver)角色:存儲與主題的狀態自恰的狀態。具體現察者角色實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題的狀態相協調。如果需要,具體現察者角色可以保存一個指向具體主題對象的引用。具體觀察者角色通常用一個具體子類實現。

從具體主題角色指向抽象觀察者角色的合成關係,代表具體主題對象可以有任意多個對抽象觀察者對象的引用。之所以使用抽象觀察者而不是具體觀察者,意味着主題對象不需要知道引用了哪些ConcreteObserver類型,而只知道抽象Observer類型。這就使得具體主題對象可以動態地維護一系列的對觀察者對象的引用,並在需要的時候調用每一個觀察者共有的Update()方法。這種做法叫做"針對抽象編程"。


三、 觀察者模式的示意性源代碼

None.gif// Observer pattern -- Structural example  
None.gif
using System;
None.gif
using System.Collections;
None.gif
None.gif
// "Subject"
None.gif
abstract class Subject
ExpandedBlockStart.gif
{
InBlock.gif  
// Fields
InBlock.gif
  private ArrayList observers = new ArrayList();
InBlock.gif
InBlock.gif  
// Methods
InBlock.gif
  public void Attach( Observer observer )
ExpandedSubBlockStart.gif  
{
InBlock.gif    observers.Add( observer );
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  
public void Detach( Observer observer )
ExpandedSubBlockStart.gif  
{
InBlock.gif    observers.Remove( observer );
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  
public void Notify()
ExpandedSubBlockStart.gif  
{
InBlock.gif    
foreach( Observer o in observers )
InBlock.gif      o.Update();
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
None.gif
// "ConcreteSubject"
None.gif
class ConcreteSubject : Subject
ExpandedBlockStart.gif
{
InBlock.gif  
// Fields
InBlock.gif
  private string subjectState;
InBlock.gif
InBlock.gif  
// Properties
InBlock.gif
  public string SubjectState
ExpandedSubBlockStart.gif  
{
ExpandedSubBlockStart.gif    
getreturn subjectState; }
ExpandedSubBlockStart.gif    
set{ subjectState = value; }
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
None.gif
// "Observer"
None.gif
abstract class Observer
ExpandedBlockStart.gif
{
InBlock.gif  
// Methods
InBlock.gif
  abstract public void Update();
ExpandedBlockEnd.gif}

None.gif
None.gif
// "ConcreteObserver"
None.gif
class ConcreteObserver : Observer
ExpandedBlockStart.gif
{
InBlock.gif  
// Fields
InBlock.gif
  private string name;
InBlock.gif  
private string observerState;
InBlock.gif  
private ConcreteSubject subject;
InBlock.gif
InBlock.gif  
// Constructors
InBlock.gif
  public ConcreteObserver( ConcreteSubject subject,  
InBlock.gif    
string name )
ExpandedSubBlockStart.gif  
{
InBlock.gif    
this.subject = subject;
InBlock.gif    
this.name = name;
ExpandedSubBlockEnd.gif  }

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

InBlock.gif
InBlock.gif  
// Properties
InBlock.gif
  public ConcreteSubject Subject
ExpandedSubBlockStart.gif  
{
ExpandedSubBlockStart.gif    
get return subject; }
ExpandedSubBlockStart.gif    
set { subject = value; }
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
ExpandedBlockStart.gif
/// <summary>
InBlock.gif
/// Client test
ExpandedBlockEnd.gif
/// </summary>

None.gifpublic class Client
ExpandedBlockStart.gif
{
InBlock.gif  
public static void Main( string[] args )
ExpandedSubBlockStart.gif  
{
InBlock.gif    
// Configure Observer structure
InBlock.gif
    ConcreteSubject s = new ConcreteSubject();
InBlock.gif    s.Attach( 
new ConcreteObserver( s, "1" ) );
InBlock.gif    s.Attach( 
new ConcreteObserver( s, "2" ) );
InBlock.gif    s.Attach( 
new ConcreteObserver( s, "3" ) );
InBlock.gif
InBlock.gif    
// Change subject and notify observers
InBlock.gif
    s.SubjectState = "ABC";
InBlock.gif    s.Notify();
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}


四、 C#中的Delegate與Event

實際上在C#中實現Observer模式沒有這麼辛苦,.NET中提供了Delegate與Event機制,我們可以利用這種機制簡化Observer模式。關於Delegate與Event的使用方法請參考相關文檔。改進後的Observer模式實現如下:

None.gif// Observer pattern -- Structural example  
None.gif
using System;
None.gif
None.gif
//Delegate
None.gif
delegate void UpdateDelegate(); 
None.gif
None.gif
//Subject
None.gif
class Subject
ExpandedBlockStart.gif
{
InBlock.gif  
public event UpdateDelegate UpdateHandler;
InBlock.gif  
InBlock.gif  
// Methods
InBlock.gif
  public void Attach( UpdateDelegate ud )
ExpandedSubBlockStart.gif  
{
InBlock.gif    UpdateHandler 
+= ud;
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  
public void Detach( UpdateDelegate ud )
ExpandedSubBlockStart.gif  
{
InBlock.gif    UpdateHandler 
-= ud;
ExpandedSubBlockEnd.gif  }

InBlock.gif  
InBlock.gif  
public void Notify()
ExpandedSubBlockStart.gif  
{
InBlock.gif    
if(UpdateHandler != null) UpdateHandler();
ExpandedSubBlockEnd.gif  }

InBlock.gif
ExpandedBlockEnd.gif}

None.gif
None.gif
//ConcreteSubject
None.gif
class ConcreteSubject : Subject
ExpandedBlockStart.gif
{
InBlock.gif  
// Fields
InBlock.gif
  private string subjectState;
InBlock.gif
InBlock.gif  
// Properties
InBlock.gif
  public string SubjectState
ExpandedSubBlockStart.gif  
{
ExpandedSubBlockStart.gif    
getreturn subjectState; }
ExpandedSubBlockStart.gif    
set{ subjectState = value; }
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
None.gif
// "ConcreteObserver"
None.gif
class ConcreteObserver
ExpandedBlockStart.gif
{
InBlock.gif  
// Fields
InBlock.gif
  private string name;
InBlock.gif  
private string observerState;
InBlock.gif  
private ConcreteSubject subject;
InBlock.gif
InBlock.gif  
// Constructors
InBlock.gif
  public ConcreteObserver( ConcreteSubject subject,  
InBlock.gif    
string name )
ExpandedSubBlockStart.gif  
{
InBlock.gif    
this.subject = subject;
InBlock.gif    
this.name = name;
ExpandedSubBlockEnd.gif  }

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

InBlock.gif
InBlock.gif  
// Properties
InBlock.gif
  public ConcreteSubject Subject
ExpandedSubBlockStart.gif  
{
ExpandedSubBlockStart.gif    
get return subject; }
ExpandedSubBlockStart.gif    
set { subject = value; }
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
None.gif
// "ConcreteObserver"
None.gif
class AnotherObserver
ExpandedBlockStart.gif
{
InBlock.gif  
// Methods
InBlock.gif
  public void Show()
ExpandedSubBlockStart.gif  
{
InBlock.gif    Console.WriteLine(
"AnotherObserver got an Notification!");
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
None.gif
public class Client
ExpandedBlockStart.gif

InBlock.gif  
public static void Main(string[] args)
ExpandedSubBlockStart.gif  

InBlock.gif    ConcreteSubject s 
= new ConcreteSubject();
InBlock.gif    ConcreteObserver o1 
= new ConcreteObserver(s, "1");
InBlock.gif    ConcreteObserver o2 
= new ConcreteObserver(s, "2");
InBlock.gif    AnotherObserver o3 
= new AnotherObserver();
InBlock.gif    
InBlock.gif    s.Attach(
new UpdateDelegate(o1.Update));
InBlock.gif    s.Attach(
new UpdateDelegate(o2.Update));
InBlock.gif    s.Attach(
new UpdateDelegate(o3.Show));
InBlock.gif
InBlock.gif    s.SubjectState 
= "ABC";
InBlock.gif    s.Notify();
InBlock.gif
InBlock.gif    Console.WriteLine(
"--------------------------");
InBlock.gif    s.Detach(
new UpdateDelegate(o1.Update));
InBlock.gif
InBlock.gif    s.SubjectState 
= "DEF";
InBlock.gif    s.Notify();
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

其中,關鍵的代碼如下:

None.gifdelegate void UpdateDelegate(); 

定義一個Delegate,用來規範函數結構。不管是ConcreteObserver類的Update方法還是AnotherObserver類的Show方法都符合該Delegate。這不象用Observer接口來規範必須使用Update方法那麼嚴格。只要符合Delegate所指定的方法結構的方法都可以在後面被事件所處理。

None.gifpublic event UpdateDelegate UpdateHandler;

定義一個事件,一旦觸發,可以調用一組符合UpdateDelegate規範的方法。

None.gif  public void Attach( UpdateDelegate ud )
ExpandedBlockStart.gif  
{
InBlock.gif    UpdateHandler 
+= ud;
ExpandedBlockEnd.gif  }

訂閱事件。只要是一個滿足UpdateDelegate的方法,就可以進行訂閱操作(如下所示)。

None.gif    s.Attach(new UpdateDelegate(o1.Update));
None.gif    s.Attach(
new UpdateDelegate(o2.Update));
None.gif    s.Attach(
new UpdateDelegate(o3.Show));

在Notify方法中:

None.gif  public void Notify()
ExpandedBlockStart.gif  
{
InBlock.gif    
if(UpdateHandler != null) UpdateHandler();
ExpandedBlockEnd.gif  }

只要UpdateHandler != null(表示有訂閱者),就可以觸發事件(UpdateHandler()),所有的訂閱者便會接到通知。


五、 一個實際應用觀察者模式的例子

該例子演示了註冊的投資者在股票市場發生變化時,可以自動得到通知。該例子仍然使用的是傳統的Observer處理手段,至於如何轉換成Delegate與Event留給讀者自己考慮。

None.gif// Observer pattern -- Real World example  
None.gif
using System;
None.gif
using System.Collections;
None.gif
None.gif
// "Subject"
None.gif
abstract class Stock
ExpandedBlockStart.gif
{
InBlock.gif  
// Fields
InBlock.gif
  protected string symbol;
InBlock.gif  
protected double price;
InBlock.gif  
private ArrayList investors = new ArrayList();
InBlock.gif
InBlock.gif  
// Constructor
InBlock.gif
  public Stock( string symbol, double price )
ExpandedSubBlockStart.gif  
{
InBlock.gif    
this.symbol = symbol;
InBlock.gif    
this.price = price;
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  
// Methods
InBlock.gif
  public void Attach( Investor investor )
ExpandedSubBlockStart.gif  
{
InBlock.gif    investors.Add( investor );
ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  
public void Detach( Investor investor )
ExpandedSubBlockStart.gif  
{
InBlock.gif    investors.Remove( investor );
ExpandedSubBlockEnd.gif  }

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

InBlock.gif
InBlock.gif  
// Properties
InBlock.gif
  public double Price
ExpandedSubBlockStart.gif  
{
ExpandedSubBlockStart.gif    
getreturn price; }
InBlock.gif    
set
ExpandedSubBlockStart.gif    
{
InBlock.gif      price 
= value;
InBlock.gif      Notify(); 
ExpandedSubBlockEnd.gif    }

ExpandedSubBlockEnd.gif  }

InBlock.gif
InBlock.gif  
public string Symbol
ExpandedSubBlockStart.gif  
{
ExpandedSubBlockStart.gif    
getreturn symbol; }
ExpandedSubBlockStart.gif    
set{ symbol = value; }
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
None.gif
// "ConcreteSubject"
None.gif
class IBM : Stock
ExpandedBlockStart.gif
{
InBlock.gif  
// Constructor
InBlock.gif
  public IBM( string symbol, double price )
ExpandedSubBlockStart.gif    : 
base( symbol, price ) {}
ExpandedBlockEnd.gif}

None.gif
None.gif
// "Observer"
None.gif
interface IInvestor
ExpandedBlockStart.gif
{
InBlock.gif  
// Methods
InBlock.gif
  void Update( Stock stock );
ExpandedBlockEnd.gif}

None.gif
None.gif
// "ConcreteObserver"
None.gif
class Investor : IInvestor
ExpandedBlockStart.gif
{
InBlock.gif  
// Fields
InBlock.gif
  private string name;
InBlock.gif  
private string observerState;
InBlock.gif  
private Stock stock;
InBlock.gif
InBlock.gif  
// Constructors
InBlock.gif
  public Investor( string name )
ExpandedSubBlockStart.gif  
{
InBlock.gif    
this.name = name;
ExpandedSubBlockEnd.gif  }

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

InBlock.gif
InBlock.gif  
// Properties
InBlock.gif
  public Stock Stock
ExpandedSubBlockStart.gif  
{
ExpandedSubBlockStart.gif    
getreturn stock; }
ExpandedSubBlockStart.gif    
set{ stock = value; }
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}

None.gif
ExpandedBlockStart.gif
/// <summary>
InBlock.gif
/// ObserverApp test
ExpandedBlockEnd.gif
/// </summary>

None.gifpublic class ObserverApp
ExpandedBlockStart.gif
{
InBlock.gif  
public static void Main( string[] args )
ExpandedSubBlockStart.gif  
{
InBlock.gif    
// Create investors
InBlock.gif
    Investor s = new Investor( "Sorros" );
InBlock.gif    Investor b 
= new Investor( "Berkshire" );
InBlock.gif
InBlock.gif    
// Create IBM stock and attach investors
InBlock.gif
    IBM ibm = new IBM( "IBM"120.00 );
InBlock.gif    ibm.Attach( s );
InBlock.gif    ibm.Attach( b );
InBlock.gif
InBlock.gif    
// Change price, which notifies investors
InBlock.gif
    ibm.Price = 120.10;
InBlock.gif    ibm.Price 
= 121.00;
InBlock.gif    ibm.Price 
= 120.50;
InBlock.gif    ibm.Price 
= 120.75;
ExpandedSubBlockEnd.gif  }

ExpandedBlockEnd.gif}


六、 觀察者模式的優缺點

Observer模式的優點是實現了表示層和數據邏輯層的分離,並定義了穩定的更新消息傳遞機制,類別清晰,並抽象了更新接口,使得可以有各種各樣不同的表示層(觀察者)。

但是其缺點是每個外觀對象必須繼承這個抽像出來的接口類,這樣就造成了一些不方便,比如有一個別人寫的外觀對象,並沒有繼承該抽象類,或者接口不對,我們又希望不修改該類直接使用它。雖然可以再應用Adapter模式來一定程度上解決這個問題,但是會造成更加複雜煩瑣的設計,增加出錯機率。

觀察者模式的效果有以下幾個優點:

(1)觀察者模式在被觀察者和觀察者之間建立一個抽象的耦合。被觀察者角色所知道的只是一個具體現察者聚集,每一個具體現察者都符合一個抽象觀察者的接口。被觀察者並不認識任何一個具體觀察者,它只知道它們都有一個共同的接口。由於被觀察者和觀察者沒有緊密地耦合在一起,因此它們可以屬於不同的抽象化層次。

(2)觀察者模式支持廣播通信。被觀察者會向所有的登記過的觀察者發出通知。

觀察者模式有下面的一些缺點:

(1)如果一個被觀察者對象有很多直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。

(2)如果在被觀察者之間有循環依賴的話,被觀察者會觸發它們之間進行循環調用,導致系統崩潰。在使用觀察考模式時要特別注意這一點。

(3)如果對觀察者的通知是通過另外的線程進行異步投遞的話,系統必須保證投遞是以自恰的方式進行的。

(4)雖然觀察者模式可以隨時使觀察者知道所觀察的對象發生了變化,但是觀察者模式沒有相應的機制使觀察者知道所觀察的對象是怎麼發生變化的。



參考文獻:
閻宏,《Java與模式》,電子工業出版社
[美]James W. Cooper,《C#設計模式》,電子工業出版社
[美]Alan Shalloway  James R. Trott,《Design Patterns Explained》,中國電力出版社
[美]Robert C. Martin,《敏捷軟件開發-原則、模式與實踐》,清華大學出版社
[美]Don Box, Chris Sells,《.NET本質論 第1卷:公共語言運行庫》,中國電力出版社

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章