觀察者模式之一

簡單地說,觀察者模式定義了一個一對多的依賴關係,讓一個或多個觀察者對象監察一個主題對象。這樣一個主題對象在狀態上的變化可以通知全部的依賴於此對象的那些觀察者對象,使這些觀察者對象可以自動更新。

  觀察者模式的結構

  觀察者(Observer)模式是對象的行爲型模式,又叫作發表-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-收聽者(Source/Listener)模式或從屬者(Dependents)模式。

  本模式的類圖結構以下:

java


圖一、觀察者模式的靜態結構可從類圖中看清楚。

  在觀察者模式裏有以下的角色:

  . 抽象主題(Subject)角色:主題角色把全部的觀察者對象的引用保存在一個列表裏;每一個主題均可以有任何數量的觀察者。主題提供一個接口能夠加上或撤銷觀察者對象;主題角色又叫作抽象被觀察者(Observable)角色;
 框架


圖二、抽象主題角色,有時又叫作抽象被觀察者角色,能夠用一個抽象類或者一個接口實現;在具體的狀況下也不排除使用具體類實現。

  . 抽象觀察者(Observer)角色:爲全部的具體觀察者定義一個接口,在獲得通知時更新本身;

 異步


圖三、抽象觀察者角色,能夠用一個抽象類或者一個接口實現;在具體的狀況下也不排除使用具體類實現。

  . 具體主題(ConcreteSubject)角色:保存對具體觀察者對象有用的內部狀態;在這種內部狀態改變時給其觀察者發出一個通知;具體主題角色又叫做具體被觀察者角色;
 ide


圖四、具體主題角色,一般用一個具體子類實現。

  .具體觀察者(ConcreteObserver)角色:保存一個指向具體主題對象的引用;和一個與主題的狀態相符的狀態。具體觀察者角色實現抽象觀察者角色所要求的更新本身的接口,以便使自己的狀態與主題的狀態自恰。
 this


圖五、具體觀察者角色,一般用一個具體子類實現。

  下面給出一個示意性實現的Java代碼。首先在這個示意性的實現裏,用一個Java接口實現抽象主題角色,這就是下面的Subject接口:
 線程


public interface Subject
{
public void attach(Observer observer);

public void detach(Observer observer);

void notifyObservers();
}
 
代碼清單一、Subject接口的源代碼。

  這個抽象主題接口規定出三個子類必須實現的操做,即 attach() 用來增長一個觀察者對象;detach() 用來刪除一個觀察者對象;和notifyObservers() 用來通知各個觀察者刷新它們本身。抽象主題角色實際上要求子類保持一個以全部的觀察者對象爲元素的列表。

  具體主題則是實現了抽象主題Subject接口的一個具體類,它給出了以上的三個操做的具體實現。從下面的源代碼能夠看出,這裏給出的Java實現使用了一個Java向量來保存全部的觀察者對象,而 attach() 和 detach() 操做則是對此向量的元素增減操做。


import java.util.Vector;
import java.util.Enumeration;

public class ConcreteSubject implements Subject
{
public void attach(Observer observer)
{
observersVector.addElement(observer);
}

public void detach(Observer observer)
{
observersVector.removeElement(observer);
}

public void notifyObservers()
{
Enumeration enumeration = observers();
while (enumeration.hasMoreElements())
{
((Observer)enumeration.nextElement()).update();
}
}

public Enumeration observers()
{
return ((Vector) observersVector.clone()).elements();
}
private Vector observersVector = new java.util.Vector();
}
 
代碼清單二、ConcreteSubject類的源代碼。

  抽象觀察者角色的實現其實是最爲簡單的一個,它是一個Java接口,只聲明瞭一個方法,即update()。這個方法被子類實現後,一被調用便刷新本身。

public interface Observer
{
void update();
}
代碼清單三、Observer接口的源代碼。

  具體觀察者角色的實現其實只涉及update()方法的實現。這個方法怎麼實現與應用密切相關,所以本類只給出一個框架。
public class ConcreteObserver implements Observer
{
public void update()
{
// Write your code here
}
}
代碼清單四、ConcreteObserver類的源代碼。

  雖然觀察者模式的實現方法能夠有設計師本身肯定,可是由於從AWT1.1開始視窗系統的事件模型採用觀察者模式,所以觀察者模式在Java語言裏的地位較爲重要。正由於這個緣由,Java語言給出了它本身對觀察者模式的支持。所以,本文建議讀者在本身的系統中應用觀察者模式時,不妨利用Java語言所提供的支持。
   Java語言提供的對觀察者模式的支持

  在Java語言的java.util庫裏面,提供了一個Observable類以及一個Observer接口,構成Java語言對觀察者模式的支持。

   Observer接口

  這個接口只定義了一個方法,update()。當被觀察者對象的狀態發生變化時,這個方法就會被調用。這個方法的實現應當調用每個被觀察者對象的notifyObservers()方法,從而通知全部的觀察對象。


圖六、java.util提供的Observer接口的類圖。


package java.util;

public interface Observer
{
/**
* 當被觀察的對象發生變化時,這個方法會被調用。
*/
void update(Observable o, Object arg);
}
 
代碼清單五、java.util.Observer接口的源代碼。

   Observable類

  被觀察者類都是java.util.Observable類的子類。java.util.Observable提供公開的方法支持觀察者對象,這些方法中有兩個對Observable的子類很是重要:一個是setChanged(),另外一個是notifyObservers()。第一個方法setChanged()被調用以後會設置一個內部標記變量,表明被觀察者對象的狀態發生了變化。第二個是notifyObservers(),這個方法被調用時,會調用全部登記過的觀察者對象的update()方法,使這些觀察者對象能夠更新本身。

  java.util.Observable類還有其它的一些重要的方法。好比,觀察者對象能夠調用java.util.Observable類的addObserver()方法,將對象一個一個加入到一個列表上。當有變化時,這個列表能夠告訴notifyObservers()方法那些觀察者對象須要通知。因爲這個列表是私有的,所以java.util.Observable的子對象並不知道觀察者對象一直在觀察着它們。


圖七、Java語言提供的被觀察者的類圖。

  被觀察者類Observable的源代碼:


package java.util;
public class Observable
{
private boolean changed = false;
private Vector obs;

/** 用0個觀察者構造一個被觀察者。**/

public Observable()
{
obs = new Vector();
}

/**
* 將一個觀察者加到觀察者列表上面。
*/
public synchronized void addObserver(Observer o)
{
if (!obs.contains(o))
{
obs.addElement(o);
}
}

/**
* 將一個觀察者對象從觀察者列表上刪除。
*/
public synchronized void deleteObserver(Observer o)
{
obs.removeElement(o);
}

/**
* 至關於 notifyObservers(null)
*/
public void notifyObservers()
{
notifyObservers(null);
}

/**
* 若是本對象有變化(那時hasChanged 方法會返回true)
* 調用本方法通知全部登記在案的觀察者,即調用它們的update()方法,
* 傳入this和arg做爲參量。
*/
public void notifyObservers(Object arg)
{
/**
* 臨時存放當前的觀察者的狀態。參見備忘錄模式。
*/
Object[] arrLocal;

synchronized (this)
{
if (!changed) return;
arrLocal = obs.toArray();
clearChanged();
}

for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}

/**
* 將觀察者列表清空
*/
public synchronized void deleteObservers()
{
obs.removeAllElements();
}

/**
* 將「已變化」設爲true
*/
protected synchronized void setChanged()
{
changed = true;
}

/**
* 將「已變化」重置爲false
*/
protected synchronized void clearChanged()
{
changed = false;
}

/**
* 探測本對象是否已變化
*/
public synchronized boolean hasChanged()
{
return changed;
}

/**
* 返還被觀察對象(即此對象)的觀察者總數。
*/
public synchronized int countObservers()
{
return obs.size();
}
}
 
代碼清單六、java.util.Observer接口的源代碼。

  這個Observable類表明一個被觀察者對象。一個被觀察者對象能夠有數個觀察者對象,一個觀察者能夠是一個實現Observer接口的對象。在被觀察者對象發生變化時,它會調用Observable的notifyObservers方法,此方法調用全部的具體觀察者的update()方法,從而使全部的觀察者都被通知更新本身。見下面的類圖:


圖八、使用Java語言提供的對觀察者模式的支持。

  發通知的次序在這裏沒有指明。Observerable類所提供的缺省實現會按照Observers對象被登記的次序通知它們,可是Observerable類的子類能夠改掉這一次序。子類並能夠在單獨的線程裏通知觀察者對象;或者在一個公用的線程裏按照次序執行。

  當一個可觀察者對象剛剛創立時,它的觀察者集合是空的。兩個觀察者對象在它們的equals()方法返回true時,被認爲是兩個相等的對象。
   怎樣使用Java對觀察者模式的支持

  爲了說明怎樣使用Java所提供的對觀察者模式的支持,本節給出一個很是簡單的例子。在這個例子裏,被觀察對象叫作Watched,也就是被監視者;而觀察者對象叫作Watcher。Watched對象繼承自java.util.Obsevable類;而Watcher對象實現了java.util.Observer接口。另外有一個對象Tester,扮演客戶端的角色。

  這個簡單的系統的結構以下圖所示。


圖九、一個使用Observer接口和Observable類的例子。

  在客戶端改變Watched對象的內部狀態時,Watched就會通知Watcher採起必要的行動。


package com.javapatterns.observer.watching;

import java.util.Observer;

public class Tester
{
static private Watched watched;
static private Observer watcher;

public static void main(String[] args)
{
watched = new Watched();

watcher = new Watcher(watched);

watched.changeData("In C, we create bugs.");
watched.changeData("In Java, we inherit bugs.");
watched.changeData("In Java, we inherit bugs.");
watched.changeData("In Visual Basic, we visualize bugs.");
}
}
 

  代碼清單七、Tester類的源代碼。


package com.javapatterns.observer.watching;

import java.util.Observable;

public class Watched extends Observable
{
private String data = "";

public String retrieveData()
{
return data;
}

public void changeData(String data)
{
if ( !this.data.equals( data) )
{
this.data = data;
setChanged();
}

notifyObservers();
}
}
 

  代碼清單八、Watched類的源代碼。


package com.javapatterns.observer.watching;

import java.util.Observable;
import java.util.Observer;

public class Watcher implements Observer
{
public Watcher(Watched w)
{
w.addObserver(this);
}

public void update( Observable ob, Object arg)
{
System.out.println("Data has been changed to: '" + ((Watched)ob).retrieveData() + "'");
}
}
 

  代碼清單九、Watcher類的源代碼。

  能夠看出,雖然客戶端將Watched對象的內部狀態賦值了四次,可是值的改變只有三次:

watched.changeData("In C, we create bugs.");
watched.changeData("In Java, we inherit bugs.");
watched.changeData("In Java, we inherit bugs.");
watched.changeData("In Visual Basic, we visualize bugs.");

  代碼清單十、被觀察者的內部狀態發生了改變。

  對應地,Watcher對象彙報了三次改變,下面就是運行時間程序打印出的信息:

Data has been changed to: 'In C, we create bugs.'

Data has been changed to: 'In Java, we inherit bugs.'

Data has been changed to: 'In Visual Basic, we visualize bugs.'
 

  代碼清單十一、運行的結果。

   菩薩的守瓶龜

  想當年齊天大聖爲解救師傅唐僧,前往南海普陀山請菩薩降伏妖怪紅孩兒:「菩薩據說...恨了一聲,將手中寶珠淨瓶往海內心撲的一摜...只見那海當中,翻波跳浪,鑽出個瓶來,原來是一個怪物馱着出來...要知此怪名和姓,興風做浪惡烏龜。」

  使用面向對象的語言描述,烏龜即是一個觀察者對象,它觀察的主題是菩薩。一旦菩薩將淨瓶摜到海里,就象徵着菩薩做爲主題調用了notifyObservers()方法。在西遊記中,觀察者對象有兩個,一個是烏龜,另外一個是悟空。悟空的反應在這裏暫時不考慮,而烏龜的反應即是將瓶子馱回海岸。


圖十、菩薩和菩薩的守瓶烏龜。

 
  菩薩做爲被觀察者對象,繼承自Observable類;而守瓶烏龜做爲觀察者,繼承自Observer接口;這個模擬系統的實現能夠採用Java對觀察者模式的支持達成。設計

  Java中的DEM事件機制

  AWT中的DEM機制

  責任鏈模式一章中曾談到,AWT1.0的事件處理的模型是基於責任鏈的。這種模型不適用於複雜的系統,所以在AWT1.1版本及之後的各個版本中,事件處理模型均爲基於觀察者模式的委派事件模型(Delegation Event Model或DEM)。

  在DEM模型裏面,主題(Subject)角色負責發佈(publish)事件,而觀察者角色向特定的主題訂閱(subscribe)它所感興趣的事件。當一個具體主題產生一個事件時,它就會通知全部感興趣的訂閱者。

  使用這種發佈-訂閱機制的基本設計目標,是提供一種將發佈者與訂閱者鬆散地耦合在一塊兒的聯繫形式,以及一種可以動態地登記、取消向一個發佈者的訂閱請求的辦法。顯然,實現這一構思的技巧,是設計抽象接口,並把抽象層和具體層分開。這在觀察者模式裏能夠清楚地看到。

  使用DEM的用詞,發佈者叫作事件源(event source),而訂閱者叫作事件聆聽者(event listener)。在Java裏面,事件由類表明,事件的發佈是經過同步地調用成員方法作到的。

  Servlet技術中的的DEM機制

  AWT中所使用的DEM事件模型實際上被應用到了全部的Java事件機制上。Servlet技術中的事件處理機制一樣也是使用的DEM模型。

  SAX2技術中的DEM機制

  DEM事件模型也被應用到了SAX2的事件處理機制上。

  觀察者模式的效果

  觀察者模式的效果有如下的優勢

  第1、觀察者模式在被觀察者和觀察者之間創建一個抽象的耦合。被觀察者角色所知道的只是一個具體觀察者列表,每個具體觀察者都符合一個抽象觀察者的接口。被觀察者並不認識任何一個具體觀察者,它只知道它們都有一個共同的接口。

  因爲被觀察者和觀察者沒有緊密地耦合在一塊兒,所以它們能夠屬於不一樣的抽象化層次。若是被觀察者和觀察者都被扔到一塊兒,那麼這個對象必然跨越抽象化和具體化層次。

  第2、觀察者模式支持廣播通信。被觀察者會向全部的登記過的觀察者發出通知,

  觀察者模式有下面的缺點

  第1、若是一個被觀察者對象有不少的直接和間接的觀察者的話,將全部的觀察者都通知到會花費不少時間。

  第2、若是在被觀察者之間有循環依賴的話,被觀察者會觸發它們之間進行循環調用,致使系統崩潰。在使用觀察者模式是要特別注意這一點。

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

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

  觀察者模式與其它模式的關係

  觀察者模式使用了備忘錄模式(Memento Pattern)暫時將觀察者對象存儲在被觀察者對象裏面。

  問答題

  第一題、我和妹妹跟媽媽說:「媽媽,我和妹妹在院子裏玩;飯作好了叫咱們一聲。」請問這是什麼模式?可否給出類圖說明?

  問答題答案

  第一題答案、這是觀察者模式。我和妹妹讓媽媽告訴咱們飯作好了,這樣咱們就能夠來吃飯了。換用較爲技術化的語言來講,當系統的主題(飯)發生變化時,就告訴系統的其它部份(觀察者們,也就是媽媽、我和妹妹),使其能夠調整內部狀態(有開始吃飯的準備),並採起相應的行動(吃飯)。

  系統的類圖說明以下。

code

圖十一、系統的類圖。
相關文章
相關標籤/搜索