簡單地說,觀察者模式定義了一個一對多的依賴關係,讓一個或多個觀察者對象監察一個主題對象。這樣一個主題對象在狀態上的變化可以通知全部的依賴於此對象的那些觀察者對象,使這些觀察者對象可以自動更新。
觀察者模式的結構
觀察者(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(); } |
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(); } |
public interface Observer { void update(); } |
public class ConcreteObserver implements Observer { public void update() { // Write your code here } } |
![]() 圖六、java.util提供的Observer接口的類圖。 |
package java.util; public interface Observer { /** * 當被觀察的對象發生變化時,這個方法會被調用。 */ void update(Observable o, Object arg); } |
![]() 圖七、Java語言提供的被觀察者的類圖。 |
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語言提供的對觀察者模式的支持。 |
![]() 圖九、一個使用Observer接口和Observable類的例子。 |
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."); } } |
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(); } } |
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() + "'"); } } |
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."); |
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.' |
![]() 圖十、菩薩和菩薩的守瓶烏龜。 |
菩薩做爲被觀察者對象,繼承自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
![]() |