我的漫漫程序之旅

          專注于JavaWeb開發(fā)
          隨筆 - 39, 文章 - 310, 評論 - 411, 引用 - 0
          數(shù)據(jù)加載中……

          Java 觀察者模式的淺析

          簡單地說,觀察者模式定義了一個一對多的依賴關系,讓一個或多個觀察者對象監(jiān)察一個主題對象。這樣一個主題對象在狀態(tài)上的變化能夠通知所有的依賴于此對象的那些觀察者對象,使這些觀察者對象能夠自動更新。

            觀察者模式的結構

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

            本模式的類圖結構如下:


          圖1、觀察者模式的靜態(tài)結構可從類圖中看清楚。

            在觀察者模式里有如下的角色:

            . 抽象主題(Subject)角色:主題角色把所有的觀察者對象的引用保存在一個列表里;每個主題都可以有任何數(shù)量的觀察者。主題提供一個接口可以加上或撤銷觀察者對象;主題角色又叫做抽象被觀察者(Observable)角色;


          圖2、抽象主題角色,有時又叫做抽象被觀察者角色,可以用一個抽象類或者一個接口實現(xiàn);在具體的情況下也不排除使用具體類實現(xiàn)。

            . 抽象觀察者(Observer)角色:為所有的具體觀察者定義一個接口,在得到通知時更新自己;


          圖3、抽象觀察者角色,可以用一個抽象類或者一個接口實現(xiàn);在具體的情況下也不排除使用具體類實現(xiàn)。

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


          圖4、具體主題角色,通常用一個具體子類實現(xiàn)。

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


          圖5、具體觀察者角色,通常用一個具體子類實現(xiàn)。

            下面給出一個示意性實現(xiàn)的Java代碼。首先在這個示意性的實現(xiàn)里,用一個Java接口實現(xiàn)抽象主題角色,這就是下面的Subject接口:


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

          public void detach(Observer observer);

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

            這個抽象主題接口規(guī)定出三個子類必須實現(xiàn)的操作,即 attach() 用來增加一個觀察者對象;detach() 用來刪除一個觀察者對象;和notifyObservers() 用來通知各個觀察者刷新它們自己。抽象主題角色實際上要求子類保持一個以所有的觀察者對象為元素的列表。

            具體主題則是實現(xiàn)了抽象主題Subject接口的一個具體類,它給出了以上的三個操作的具體實現(xiàn)。從下面的源代碼可以看出,這里給出的Java實現(xiàn)使用了一個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();
          }
          代碼清單2、ConcreteSubject類的源代碼。

            抽象觀察者角色的實現(xiàn)實際上是最為簡單的一個,它是一個Java接口,只聲明了一個方法,即update()。這個方法被子類實現(xiàn)后,一被調用便刷新自己。

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

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

            雖然觀察者模式的實現(xiàn)方法可以有設計師自己確定,但是因為從AWT1.1開始視窗系統(tǒng)的事件模型采用觀察者模式,因此觀察者模式在Java語言里的地位較為重要。正因為這個原因,Java語言給出了它自己對觀察者模式的支持。因此,本文建議讀者在自己的系統(tǒng)中應用觀察者模式時,不妨利用Java語言所提供的支持。
            Java語言提供的對觀察者模式的支持

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

            Observer接口

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


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


          package java.util;

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

            Observable類

            被觀察者類都是java.util.Observable類的子類。java.util.Observable提供公開的方法支持觀察者對象,這些方法中有兩個對Observable的子類非常重要:一個是setChanged(),另一個是notifyObservers()。第一個方法setChanged()被調用之后會設置一個內部標記變量,代表被觀察者對象的狀態(tài)發(fā)生了變化。第二個是notifyObservers(),這個方法被調用時,會調用所有登記過的觀察者對象的update()方法,使這些觀察者對象可以更新自己。

            java.util.Observable類還有其它的一些重要的方法。比如,觀察者對象可以調用java.util.Observable類的addObserver()方法,將對象一個一個加入到一個列表上。當有變化時,這個列表可以告訴notifyObservers()方法那些觀察者對象需要通知。由于這個列表是私有的,因此java.util.Observable的子對象并不知道觀察者對象一直在觀察著它們。


          圖7、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)
          {
          /**
          * 臨時存放當前的觀察者的狀態(tài)。參見備忘錄模式。
          */

          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;
          }


          /**
          * 返還被觀察對象(即此對象)的觀察者總數(shù)。
          */

          public synchronized int countObservers()
          {
          return obs.size();
          }

          }

          代碼清單6、java.util.Observer接口的源代碼。

            這個Observable類代表一個被觀察者對象。一個被觀察者對象可以有數(shù)個觀察者對象,一個觀察者可以是一個實現(xiàn)Observer接口的對象。在被觀察者對象發(fā)生變化時,它會調用Observable的notifyObservers方法,此方法調用所有的具體觀察者的update()方法,從而使所有的觀察者都被通知更新自己。見下面的類圖:


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

            發(fā)通知的次序在這里沒有指明。Observerable類所提供的缺省實現(xiàn)會按照Observers對象被登記的次序通知它們,但是Observerable類的子類可以改掉這一次序。子類并可以在單獨的線程里通知觀察者對象;或者在一個公用的線程里按照次序執(zhí)行。

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

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

            這個簡單的系統(tǒng)的結構如下圖所示。


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

            在客戶端改變Watched對象的內部狀態(tài)時,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."); 
          }

          }


            代碼清單7、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();
          }

          }


            代碼清單8、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() + "'");
          }

          }


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

            可以看出,雖然客戶端將Watched對象的內部狀態(tài)賦值了四次,但是值的改變只有三次:

          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."); 

            代碼清單10、被觀察者的內部狀態(tài)發(fā)生了改變。

            對應地,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.'

            代碼清單11、運行的結果。

            菩薩的守瓶龜

            想當年齊天大圣為解救師傅唐僧,前往南海普陀山請菩薩降伏妖怪紅孩兒:“菩薩聽說...恨了一聲,將手中寶珠凈瓶往海心里撲的一摜...只見那海當中,翻波跳浪,鉆出個瓶來,原來是一個怪物馱著出來...要知此怪名和姓,興風作浪惡烏龜。”

            使用面向對象的語言描述,烏龜便是一個觀察者對象,它觀察的主題是菩薩。一旦菩薩將凈瓶摜到海里,就象征著菩薩作為主題調用了notifyObservers()方法。在西游記中,觀察者對象有兩個,一個是烏龜,另一個是悟空。悟空的反應在這里暫時不考慮,而烏龜?shù)姆磻闶菍⑵孔玉W回海岸。


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

           
            菩薩作為被觀察者對象,繼承自Observable類;而守瓶烏龜作為觀察者,繼承自Observer接口;這個模擬系統(tǒng)的實現(xiàn)可以采用Java對觀察者模式的支持達成。

            Java中的DEM事件機制

            AWT中的DEM機制

            責任鏈模式一章中曾談到,AWT1.0的事件處理的模型是基于責任鏈的。這種模型不適用于復雜的系統(tǒng),因此在AWT1.1版本及以后的各個版本中,事件處理模型均為基于觀察者模式的委派事件模型(Delegation Event Model或DEM)。

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

            使用這種發(fā)布-訂閱機制的基本設計目標,是提供一種將發(fā)布者與訂閱者松散地耦合在一起的聯(lián)系形式,以及一種能夠動態(tài)地登記、取消向一個發(fā)布者的訂閱請求的辦法。顯然,實現(xiàn)這一構思的技巧,是設計抽象接口,并把抽象層和具體層分開。這在觀察者模式里可以清楚地看到。

            使用DEM的用詞,發(fā)布者叫做事件源(event source),而訂閱者叫做事件聆聽者(event listener)。在Java里面,事件由類代表,事件的發(fā)布是通過同步地調用成員方法做到的。

            Servlet技術中的的DEM機制

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

            SAX2技術中的DEM機制

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

            觀察者模式的效果

            觀察者模式的效果有以下的優(yōu)點

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

            由于被觀察者和觀察者沒有緊密地耦合在一起,因此它們可以屬于不同的抽象化層次。如果被觀察者和觀察者都被扔到一起,那么這個對象必然跨越抽象化和具體化層次。

            第二、觀察者模式支持廣播通訊。被觀察者會向所有的登記過的觀察者發(fā)出通知,

            觀察者模式有下面的缺點

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

            第二、如果在被觀察者之間有循環(huán)依賴的話,被觀察者會觸發(fā)它們之間進行循環(huán)調用,導致系統(tǒng)崩潰。在使用觀察者模式是要特別注意這一點。

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

            第四、雖然觀察者模式可以隨時使觀察者知道所觀察的對象發(fā)生了變化,但是觀察者模式?jīng)]有相應的機制使觀察者知道所觀察的對象是怎么發(fā)生變化的。

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

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

            問答題

            第一題、我和妹妹跟媽媽說:“媽媽,我和妹妹在院子里玩;飯做好了叫我們一聲。”請問這是什么模式?能否給出類圖說明?

            問答題答案

            第一題答案、這是觀察者模式。我和妹妹讓媽媽告訴我們飯做好了,這樣我們就可以來吃飯了。換用較為技術化的語言來說,當系統(tǒng)的主題(飯)發(fā)生變化時,就告訴系統(tǒng)的其它部份(觀察者們,也就是媽媽、我和妹妹),使其可以調整內部狀態(tài)(有開始吃飯的準備),并采取相應的行動(吃飯)。

            系統(tǒng)的類圖說明如下。


          圖11、系統(tǒng)的類圖。



          posted on 2008-05-24 10:13 々上善若水々 閱讀(78070) 評論(12)  編輯  收藏 所屬分類: 設計模式

          評論

          # re: Java 觀察者模式的淺析  回復  更多評論   

          很好的,謝謝,以前對這個老是不太明白,
          2011-12-27 15:22 | 無名小卒

          # re: Java 觀察者模式的淺析[未登錄]  回復  更多評論   

          寫的很詳細,不錯
          2012-07-31 16:12 | bobby

          # re: Java 觀察者模式的淺析  回復  更多評論   

          多謝了,今天面試被問了這個
          2012-09-21 13:15 | chunxi_cao

          # re: Java 觀察者模式的淺析  回復  更多評論   

          寫的挺好,詳細,有根有據(jù)
          2012-11-14 00:24 | 席步川

          # re: Java 觀察者模式的淺析  回復  更多評論   

          博主,貌似您的類圖有點問題,我也是初學者,感覺不太對,您看看
          2012-12-21 17:43 | ustcqi

          # re: Java 觀察者模式的淺析  回復  更多評論   

          很詳細
          2013-02-19 17:21 | youthflies

          # re: Java 觀察者模式的淺析  回復  更多評論   

          不錯通俗易懂~
          2013-06-24 15:27 | 紅燒獅子頭

          # re: Java 觀察者模式的淺析  回復  更多評論   

          樓主說的好
          2013-10-10 10:44 | 帝國之花

          # re: Java 觀察者模式的淺析  回復  更多評論   

          @ustcqi
          不懂別瞎扯
          2014-01-20 11:27 | 11

          # re: Java 觀察者模式的淺析  回復  更多評論   

          我在里面看到了“自恰”,請問這個概念在計算機中哪兒有講到,能不能給出自恰的一個定義或者解釋呢?謝謝!
          2014-02-21 14:19 | lim

          # re: Java 觀察者模式的淺析  回復  更多評論   

          怒贊一下,講的非常精彩
          2014-04-21 20:57 | 陌上花開

          # re: Java 觀察者模式的淺析  回復  更多評論   

          不錯
          2014-11-10 20:59 | 圣達菲似懂非懂
          主站蜘蛛池模板: 大悟县| 金门县| 通河县| 云安县| 宁安市| 中卫市| 西昌市| 连山| 兖州市| 萨嘎县| 呼伦贝尔市| 绥中县| 周宁县| 浮梁县| 南陵县| 禹城市| 高青县| 乡宁县| 报价| 永年县| 青神县| 海阳市| 应用必备| 时尚| 会泽县| 淅川县| 葫芦岛市| 遂宁市| 麦盖提县| 宜昌市| 永和县| 濮阳市| 凉山| 淳安县| 禄丰县| 乌兰县| 湟中县| 秦皇岛市| 化州市| 华容县| 苍梧县|