1. 程式人生 > >[設計模式] 觀察者模式(釋出-訂閱/Publish-subscribe模式)

[設計模式] 觀察者模式(釋出-訂閱/Publish-subscribe模式)



主要對像:

1、抽象的觀察者/Watcher/Observer 以及具體實現

2、抽象的被觀察者/Subject/Observable 以及具體實現

(1)目標和觀察者之間的關係

       按照模式的定義,目標和觀察者之間是典型的一對多的關係。

       但是要注意,如果觀察者只有一個,也是可以的,這樣就變相實現了目標和觀察者之間一對一的關係,這也使得在處理一個物件的狀態變化會影響到另一個物件的時候,也可以考慮使用觀察者模式。

       同樣的,一個觀察者也可以觀察多個目標,如果觀察者為多個目標定義的通知更新方法都是update方法的話,這會帶來麻煩,因為需要接收多個目標的通知, 如果是一個update的方法,那就需要在方法內部區分,到底這個更新的通知來自於哪一個目標,不同的目標有不同的後續操作。

       一般情況下,觀察者應該為不同的觀察者目標,定義不同的回撥方法,這樣實現最簡單,不需要在update方法內部進行區分。

(2)單向依賴

       在觀察者模式中,觀察者和目標是單向依賴的,只有觀察者依賴於目標,而目標是不會依賴於觀察者的。

       它們之間聯絡的主動權掌握在目標手中,只有目標知道什麼時候需要通知觀察者,在整個過程中,觀察者始終是被動的,被動的等待目標的通知,等待目標傳值給它。

       對目標而言,所有的觀察者都是一樣的,目標會一視同仁的對待。當然也可以通過在目標裡面進行控制,實現有區別對待觀察者,比如某些狀態變化,只需要通知部分觀察者,但那是屬於稍微變形的用法了,不屬於標準的、原始的觀察者模式了。

(3)基本的實現說明

  • 具體的目標實現物件要能維護觀察者的註冊資訊,最簡單的實現方案就如同前面的例子那樣,採用一個集合來儲存觀察者的註冊資訊。
  • 具體的目標實現物件需要維護引起通知的狀態,一般情況下是目標自身的狀態,變形使用的情況下,也可以是別的物件的狀態。
  • 具體的觀察者實現物件需要能接收目標的通知,能夠接收目標傳遞的資料,或者是能夠主動去獲取目標的資料,並進行後續處理。
  • 如果是一個觀察者觀察多個目標,那麼在觀察者的更新方法裡面,需要去判斷是來自哪一個目標的通知。一種簡單的解決方案就是擴充套件update方法,比如在方法裡面多傳遞一個引數進行區分等;還有一種更簡單的方法,那就是乾脆定義不同的回撥方法。

(4)命名建議

  • 觀察者模式又被稱為釋出-訂閱模式
  • 目標介面的定義,建議在名稱後面跟Subject
  • 觀察者介面的定義,建議在名稱後面跟Observer
  • 觀察者介面的更新方法,建議名稱為update,當然方法的引數可以根據需要定義,引數個數不限、引數型別不限

(5)觸發通知的時機

在 實現觀察者模式的時候,一定要注意觸發通知的時機,一般情況下,是在完成了狀態維護後觸發,因為通知會傳遞資料,不能夠先通知後改資料,這很容易出問題, 會導致觀察者和目標物件的狀態不一致。比如:目標一發出通知,就有觀察者來取值,結果目標還沒有更新資料,這就明顯造成了錯誤。如下示例就是有問題的了, 示例程式碼如下:

public void setContent(String content) {

//一激動,目標先發出通知了,然後才修改自己的資料,這會造成問題

    notifyAllReader();  

    this.content = content;

}

(6)相互觀察

    在某些應用裡面,可能會出現目標和觀察者相互觀察的情況。什麼意思呢,比如有兩套觀察者模式的應用,其中一套觀察者模式的實現是A物件、B物件觀察C物件;在另一套觀察者模式的實現裡面,實現的是B物件、C物件觀察A物件,那麼A物件和C物件就是在相互觀察。

    換句話說,A物件的狀態變化會引起C物件的聯動操作,反過來,C 物件的狀態變化也會引起A物件的聯動操作。對於出現這種狀況,要特別小心處理,因為可能會出現死迴圈的情況。

(7)觀察者模式的呼叫順序示意圖

在使用觀察者模式時,會很明顯的分成兩個階段,第一個階段是準備階段,也就是維護目標和觀察者關係的階段,這個階段的呼叫順序如圖12.5所示:

 

圖12.5  觀察者模式準備階段示意圖

    接下來就是實際的執行階段了,這個階段的呼叫順序如圖12.6所示:

 

圖12.6  觀察者模式執行階段示意圖

(8)通知的順序

    從理論上說,當目標物件的狀態變化後通知所有觀察者的時候,順序是不確定的,因此觀察者實現的功能,絕對不要依賴於通知的順序,也就是說,多個觀察者之間的功能是平行的,相互不應該有先後的依賴關係。

12.3.2  推模型和拉模型

       在觀察者模式的實現裡面,又分為推模型和拉模型兩種方式,什麼意思呢?

  • 推模型
        目標物件主動向觀察者推送目標的詳細資訊,不管觀察者是否需要,推送的資訊通常是目標物件的全部或部分資料,相當於是在廣播通訊。
  • 拉模型
        目標物件在通知觀察者的時候,只傳遞少量資訊,如果觀察者需要更具體的資訊,由觀察者主動到目標物件中獲取,相當於是觀察者從目標物件中拉資料。
        一般這種模型的實現中,會把目標物件自身通過update方法傳遞給觀察者,這樣在觀察者需要獲取資料的時候,就可以通過這個引用來獲取了。

根據上面的描述,發現前面的例子就是典型的拉模型,那麼推模型如何實現呢,還是來看個示例吧,這樣會比較清楚。

(1)推模型的觀察者介面

       根據前面的講述,推模型通常都是把需要傳遞的資料直接推送給觀察者物件,所以觀察者介面中的update方法的引數需要發生變化,示例程式碼如下:

/**

 * 觀察者,比如報紙的讀者

 */

public interface Observer {

    /**

     * 被通知的方法,直接把報紙的內容推送過來

     * @param content 報紙的內容

     */

    public void update(String content);

}

(2)推模型的觀察者的具體實現

       以前需要到目標物件裡面獲取自己需要的資料,現在是直接接收傳入的資料,這就是改變的地方,示例程式碼如下:

public class Reader implements Observer{

    /**

     * 讀者的姓名

     */

    private String name;

    public void update(String content) {

       //這是採用推的方式

       System.out.println(name+"收到報紙了,閱讀先。內容是==="

+content);

    }

    public String getName() {

       return name;

    }

    public void setName(String name) {

       this.name = name;

    }

}

(3)推模型的目標物件

       跟拉模型的目標實現相比,有一些變化:

  • 一個就是通知所有觀察者的方法,以前是沒有引數的,現在需要傳入需要主動推送的資料
  • 另外一個就是在迴圈通知觀察者的時候,也就是迴圈呼叫觀察者的update方法的時候,傳入的引數不同了

示例程式碼如下:

/**

 * 目標物件,作為被觀察者,使用推模型

 */

public class Subject {

    /**

     * 用來儲存註冊的觀察者物件,也就是報紙的訂閱者

     */

    private List<Observer> readers = new ArrayList<Observer>();

    /**

     * 報紙的讀者需要先向報社訂閱,先要註冊

     * @param reader 報紙的讀者

     * @return 是否註冊成功

     */

    public void attach(Observer reader) {

       readers.add(reader);

    }

    /**

     * 報紙的讀者可以取消訂閱

     * @param reader 報紙的讀者

     * @return 是否取消成功

     */

public void detach(Observer reader) {

       readers.remove(reader);

    }

    /**

     * 當每期報紙印刷出來後,就要迅速的主動的被送到讀者的手中,

     * 相當於通知讀者,讓他們知道

     * @param content 要主動推送的內容

     */

    protected void notifyObservers(String content) {

       for(Observer reader : readers){

           reader.update(content);

       }

    }

}

(4)推模型的目標具體實現

       跟拉模型相比,有一點變化,就是在呼叫通知觀察者的方法的時候,需要傳入引數了,拉模型的實現中是不需要的,示例程式碼如下:

public class NewsPaper extends Subject{

    private String content;

    public String getContent() {

       return content;

    }

    public void setContent(String content) {

       this.content = content;

       //內容有了,說明又出報紙了,那就通知所有的讀者

       notifyObservers(content);

    }

}

(5)推模型的客戶端使用

       跟拉模型一樣,沒有變化。

好了,到此就簡單的實現了拉模型的觀察者模式,去測試一下,看看效果,是不是和前面的推模型一樣呢?如果是一樣的,那就對了。

(6)關於兩種模型的比較

       兩種實現模型,在開發的時候,究竟應該使用哪一種,還是應該具體問題具體分析。這裡,只是把兩種模型進行一個簡單的比較。

  • 推模型是假定目標物件知道觀察者需要的資料;而拉模型是目標物件不知道觀察者具體需要什麼資料,沒有辦法的情況下,乾脆把自身傳給觀察者,讓觀察者自己去按需取值。
  • 推模型可能會使得觀察者物件難以複用,因為觀察者定義的update方法是按需而定義的,可能無法兼顧沒有考慮到的使用情況。這就意味著出現新情況的時候,就可能需要提供新的update方法,或者是乾脆重新實現觀察者。
        而拉模型就不會造成這樣的情況,因為拉模型下,update方法的引數是目標物件本身,這基本上是目標物件能傳遞的最大資料集合了,基本上可以適應各種情況的需要。

12.3.3  Java中的觀察者模式

       估計有些朋友在看前面的內容的時候,心裡就嘀咕上了,Java裡面不是已經有了觀察者模式的部分實現嗎,為何還要全部自己從頭做呢?

       主要是為了讓大家更好的理解觀察者模式本身,而不用受Java語言實現的限制。

       好了,下面就來看看如何利用Java中已有的功能來實現觀察者模式。在java.util包裡面有一個類Observable,它實現了大部分我們需要的目標的功能;還有一個介面Observer,它裡面定義了update的方法,就是觀察者的介面。

       因此,利用Java中已有的功能來實現觀察者模式非常簡單,跟前面完全由自己來實現觀察者模式相比有如下改變:

  • 不需要再定義觀察者和目標的介面了,JDK幫忙定義了
  • 具體的目標實現裡面不需要再維護觀察者的註冊資訊了,這個在Java中的Observable類裡面,已經幫忙實現好了
  • 觸發通知的方式有一點變化,要先呼叫setChanged方法,這個是Java為了幫助實現更精確的觸發控制而提供的功能
  • 具體觀察者的實現裡面,update方法其實能同時支援推模型和拉模型,這個是Java在定義的時候,就已經考慮進去了

好了,說了這麼多,還是看看例子會比較直觀。

(1)新的目標的實現,不再需要自己來實現Subject定義,在具體實現的時候,也不是繼承Subject了,而是改成繼承Java中定義的Observable,示例程式碼如下:

/**

 * 報紙物件,具體的目標實現

 */

public class NewsPaper extends  java.util.Observable   {

    /**

     * 報紙的具體內容

     */

    private String content;

    /**

     * 獲取報紙的具體內容

     * @return 報紙的具體內容

     */

    public String getContent() {

       return content;

    }

    /**

     * 示意,設定報紙的具體內容,相當於要出版報紙了

     * @param content 報紙的具體內容

     */

    public void setContent(String content) {

       this.content = content;

       //內容有了,說明又出新報紙了,那就通知所有的讀者

       //注意在用Java中的Observer模式的時候,下面這句話不可少

       this.setChanged();

       //然後主動通知,這裡用的是推的方式

       this.notifyObservers(this.content);

       //如果用拉的方式,這麼呼叫

       //this.notifyObservers();

    }

}

(2)再看看新的觀察者的實現,不是實現自己定義的觀察者介面,而是實現由Java提供的Observer介面,示例程式碼如下:

/**

 * 真正的讀者,為了簡單就描述一下姓名

 */

public class Reader implements   java.util.Observer   {

/**

     * 讀者的姓名

     */

    private String name;

    public String getName() {

       return name;

    }

    public void setName(String name) {

       this.name = name;

    }

public void update(Observable o, Object obj) {

       //這是採用推的方式

       System.out.println(name

+"收到報紙了,閱讀先。目標推過來的內容是==="+obj);


//這是獲取拉的資料

System.out.println(name

+"收到報紙了,閱讀先。主動到目標物件去拉的內容是==="

+((NewsPaper)o).getContent());


    }

}

(3)客戶端使用

客戶端跟前面的寫法沒有太大改變,主要在註冊閱讀者的時候,呼叫的方法跟以前不一樣了,示例程式碼如下:

public class Client {

    public static void main(String[] args) {

       //建立一個報紙,作為被觀察者

       NewsPaper subject = new NewsPaper();

       //建立閱讀者,也就是觀察者

       Reader reader1 = new Reader();

       reader1.setName("張三");

       Reader reader2 = new Reader();

       reader2.setName("李四");

       Reader reader3 = new Reader();

       reader3.setName("王五");

       //註冊閱讀者

       subject.addObserver(reader1);

       subject.addObserver(reader2);

       subject.addObserver(reader3);

       //要出報紙啦

       subject.setContent("本期內容是觀察者模式");

    }

}

趕緊測試一下,執行執行,看看結果,執行結果如下所示:

王五收到報紙了,閱讀先。目標推過來的內容是===本期內容是觀察者模式

王五收到報紙了,閱讀先。主動到目標物件去拉的內容是===本期內容是觀察者模式

李四收到報紙了,閱讀先。目標推過來的內容是===本期內容是觀察者模式

李四收到報紙了,閱讀先。主動到目標物件去拉的內容是===本期內容是觀察者模式

張三收到報紙了,閱讀先。目標推過來的內容是===本期內容是觀察者模式

張三收到報紙了,閱讀先。主動到目標物件去拉的內容是===本期內容是觀察者模式

然後好好對比自己實現觀察者模式和使用Java已有的功能來實現觀察者模式,看看有什麼不同,有什麼相同,好好體會一下。

12.3.4  觀察者模式的優缺點

l          觀察者模式實現了觀察者和目標之間的抽象耦合
    原本目標物件在狀態發生改變的時候,需要直接呼叫所有的觀察者物件,但是抽象出觀察者介面過後,目標和觀察者就只是在抽象層面上耦合了,也就是說目標只是知道觀察者介面,並不知道具體的觀察者的類,從而實現目標類和具體的觀察者類之間解耦。

l          觀察者模式實現了動態聯動
    所謂聯動,就是做一個操作會引起其它相關的操作。由於觀察者模式對觀察者註冊實行管理,那就可以在執行期間,通過動態的控制註冊的觀察者,來控制某個動作的聯動範圍,從而實現動態聯動。

l          觀察者模式支援廣播通訊
    由於目標傳送通知給觀察者是面向所有註冊的觀察者,所以每次目標通知的資訊就要對所有註冊的觀察者進行廣播。當然,也可以通過在目標上新增新的功能來限制廣播的範圍。
    在廣播通訊的時候要注意一個問題,就是相互廣播造成死迴圈的問題。比如A和B兩個物件互為觀察者和目標物件,A物件發生狀態變化,然後A來廣播資訊,B對 象接收到通知後,在處理過程中,使得B物件的狀態也發生了改變,然後B來廣播資訊,然後A物件接到通知後,又觸發廣播資訊……,如此A引起B變化,B又引 起A變化,從而一直相互廣播資訊,就造成死迴圈了。

l          觀察者模式可能會引起無謂的操作
    由於觀察者模式每次都是廣播通訊,不管觀察者需不需要,每個觀察者都會被呼叫update方法,如果觀察者不需要執行相應處理,那麼這次操作就浪費了。
    其實浪費了還好,怕就怕引起了誤更新,那就麻煩了,比如:本應該在執行這次狀態更新前把某個觀察者刪除掉,這樣通知的時候就沒有這個觀察者了,但是現在忘掉了,那麼就會引起誤操作。

12.3.5  思考觀察者模式

1:觀察者模式的本質

觀察者模式的本質:觸發聯動

       當修改目標物件的狀態的時候,就會觸發相應的通知,然後會迴圈呼叫所有註冊的觀察者物件的相應方法,其實就相當於聯動呼叫這些觀察者的方法。

       而且這個聯動還是動態的,可以通過註冊和取消註冊來控制觀察者,因而可以在程式執行期間,通過動態的控制觀察者,來變相的實現新增和刪除某些功能處理,這些功能就是觀察者在update的時候執行的功能。

同時目標物件和觀察者物件的解耦,又保證了無論觀察者發生怎樣的變化,目標物件總是能夠正確地聯動過來。

       理解這個本質對我們非常有用,對於我們識別和使用觀察者模式有非常重要的意義,尤其是在變形使用的時候,萬變不離其宗。

2:何時選用觀察者模式

建議在如下情況中,選用觀察者模式:

  • 當一個抽象模型有兩個方面,其中一個方面的操作依賴於另一個方面的狀態 變化,那麼就可以選用觀察者模式,將這兩者封裝成觀察者和目標物件,當目標物件變化的時候,依賴於它的觀察者物件也會發生相應的變化。這樣就把抽象模型的 這兩個方面分離開了,使得它們可以獨立的改變和複用。
  • 如果在更改一個物件的時候,需要同時連帶改變其它的物件,而且不知道究竟應該有多少物件需要被連帶改變,這種情況可以選用觀察者模式,被更改的那一個物件很明顯就相當於是目標物件,而需要連帶修改的多個其它物件,就作為多個觀察者物件了。
  • 當一個物件必須通知其它的物件,但是你又希望這個物件和其它被它通知的物件是鬆散耦合的,也就是說這個物件其實不想知道具體被通知的物件,這種情況可以選用觀察者模式,這個物件就相當於是目標物件,而被它通知的物件就是觀察者物件了。

12.3.6  Swing中的觀察者模式

       Java的Swing中到處都是觀察者模式的身影,比如大家熟悉的事件處理,就是典型的觀察者模式的應用。(說明一下:早期的Swing事件處理用的是職責鏈)

       Swing元件是被觀察的目標,而每個實現監聽器的類就是觀察者,監聽器的介面就是觀察者的介面,在呼叫addXXXListener方法的時候就相當於註冊觀察者。

當元件被點選,狀態發生改變的時候,就會產生相應的通知,會呼叫註冊的觀察者的方法,就是我們所實現的監聽器的方法。

從這裡還可以學一招:如何處理一個觀察者觀察多個目標物件?

你看一個Swing的應用程式,作為一個觀察者,經常會註冊觀察多個不同的目標物件,也就是同一類,既實現了按鈕元件的事件處理,又實現了文字框元件的事件處理,是怎麼做到的呢?

答 案就在監聽器介面上,這些監聽器介面就相當於觀察者介面,也就是說一個觀察者要觀察多個目標物件,只要不同的目標物件使用不同的觀察者介面就好了,當然, 這些接口裡面的方法也不相同,不再都是update方法了。這樣一來,不同的目標物件通知觀察者所呼叫的方法也就不同了,這樣在具體實現觀察者的時候,也 就實現成不同的方法,自然就區分開了。

12.3.7  簡單變形示例——區別對待觀察者

       首先宣告,這裡只是舉一個非常簡單的變形使用的例子,也可算是基本的觀察者模式的功能加強,事實上可以有很多很多的變形應用,這也是為什麼我們特別強調大家要深入理解每個設計模式,要把握每個模式的本質的原因了。

       1:範例需求

       這是一個實際系統的簡化需求:在一個水質監測系統中有這樣一個功能,當水中的雜質為正常的時候,只是通知監測人員做記錄;當為輕度汙染的時候,除了通知監 測人員做記錄外,還要通知預警人員,判斷是否需要預警;當為中度或者高度汙染的時候,除了通知監測人員做記錄外,還要通知預警人員,判斷是否需要預警,同 時還要通知監測部門領導做相應的處理。

       2:解決思路和範例程式碼

       分析上述需求就會發現,對於水質汙染這件事情,有可能會涉及到監測員、預警人員、監測部門領導,根據不同的水質汙染情況涉及到不同的人員,也就是說,監測員、預警人員、監測部門領導他們三者是平行的,職責都是處理水質汙染,但是處理的範圍不一樣。

       因此很容易套用上觀察者模式,如果把水質汙染的記錄當作被觀察的目標的話,那麼監測員、預警人員和監測部門領導就都是觀察者了。

       前面學過的觀察者模式,當目標通知觀察者的時候是全部都通知,但是現在這個需求是不同的情況來讓不同的人處理,怎麼辦呢?

       解決的方式通常有兩種,一種是目標可以通知,但是觀察者不做任何操作;另外一種是在目標裡面進行判斷,乾脆就不通知了。兩種實現方式各有千秋,這裡選擇後面一種方式來示例,這種方式能夠統一邏輯控制,並進行觀察者的統一分派,有利於業務控制和今後的擴充套件。

       還是看程式碼吧,會更直觀。

(1)先來定義觀察者的介面,這個介面跟前面的示例差別也不大,只是新加了訪問觀察人員職務的方法,示例程式碼如下:

/**

 * 水質觀察者介面定義

 */

public interface WatcherObserver {

    /**

     * 被通知的方法

     * @param subject 傳入被觀察的目標物件

     */

    public void update(WaterQualitySubject subject);

    /**

     * 設定觀察人員的職務

     * @param job 觀察人員的職務

     */

    public void setJob(String job);

    /**

     * 獲取觀察人員的職務

     * @return 觀察人員的職務

     */

    public String getJob();

}

(2)定義完介面後,來看看觀察者的具體實現,示例程式碼如下:

/**

 * 具體的觀察者實現

 */

public class Watcher implements WatcherObserver{

    /**

     * 職務

     */

    private String job;

public String getJob() {

       return this.job;

    }  

    public void setJob(String job) {

       this.job = job;

    }

    public void update(WaterQualitySubject subject) {

       //這裡採用的是拉的方式

       System.out.println(job+"獲取到通知,當前汙染級別為:"

+subject.getPolluteLevel());

    }

}

(3)接下來定義目標的父物件,跟以前相比有些改變:

  • 把父類實現成抽象的,因為在裡面要定義抽象的方法
  • 原來通知所有的觀察者的方法被去掉了,這個方法現在需要由子類去實現,要按照業務來有區別的來對待觀察者,得看看是否需要通知觀察者
  • 新新增一個水質汙染級別的業務方法,這樣在觀察者獲取目標物件的資料的時候,就不需要再知道具體的目標物件,也不需要強制造型了

示例程式碼如下:

/**

 * 定義水質監測的目標物件

 */

public abstract class WaterQualitySubject {

    /**

     * 用來儲存註冊的觀察者物件

     */

    protected List<WatcherObserver> observers =

new ArrayList<WatcherObserver>();

    /**

     * 註冊觀察者物件

     * @param observer 觀察者物件

     */

    public void attach(WatcherObserver observer) {

       observers.add(observer);

    }

    /**

     * 刪除觀察者物件

     * @param observer 觀察者物件

     */

    public void detach(WatcherObserver observer) {

       observers.remove(observer);

    }

    /**

     * 通知相應的觀察者物件

     */

    public abstract void notifyWatchers();

    /**

     * 獲取水質汙染的級別

     * @return 水質汙染的級別

     */

    public abstract int getPolluteLevel();

}

(4)接下來重點看看目標的實現,在目標物件裡面,新增一個描述汙染級別的屬性,在判斷是否需要通知觀察者的時候,不同的汙染程度對應會通知不同的觀察者,示例程式碼如下:

/**

 * 具體的水質監測物件

 */

public class WaterQuality extends WaterQualitySubject{

    /**

     * 汙染的級別,0表示正常,1表示輕度汙染,2表示中度汙染,3表示高度汙染

     */

    private int polluteLevel = 0;

    /**

     * 獲取水質汙染的級別

     * @return 水質汙染的級別

     */

    public int getPolluteLevel() {

       return polluteLevel;

    }

    /**

     * 當監測水質情況後,設定水質汙染的級別

     * @param polluteLevel 水質汙染的級別

     */

    public void setPolluteLevel(int polluteLevel) {

       this.polluteLevel = polluteLevel;

       //通知相應的觀察者

       this.notifyWatchers();

    }

    /**

     * 通知相應的觀察者物件

     */

    public void notifyWatchers() {

       //迴圈所有註冊的觀察者

       for(WatcherObserver watcher : observers){

                     //開始根據汙染級別判斷是否需要通知,由這裡總控

                     if(this.polluteLevel >= 0){

                         //通知監測員做記錄

                         if("監測人員".equals(watcher.getJob())){

                            watcher.update(this);

                         }

                     }

                     if(this.polluteLevel >= 1){

                         //通知預警人員

                         if("預警人員".equals(watcher.getJob())){

                            watcher.update(this);

                         }

                      }

                     if(this.polluteLevel >= 2){

                         //通知監測部門領導

                         if("監測部門領導".equals(

watcher.getJob())){

                            watcher.update(this);

                         }

                     }

       }

    }

}

(5)大功告成,來寫個客戶端,測試一下,示例程式碼如下:

public class Client {

    public static void main(String[] args) {

       //建立水質主題物件

       WaterQuality subject = new WaterQuality();

       //建立幾個觀察者

       WatcherObserver watcher1 = new Watcher();

       watcher1.setJob("監測人員");

       WatcherObserver watcher2 = new Watcher();

       watcher2.setJob("預警人員");

       WatcherObserver watcher3 = new Watcher();

       watcher3.setJob("監測部門領導");

       //註冊觀察者

       subject.attach(watcher1);

       subject.attach(watcher2);

       subject.attach(watcher3);

       //填寫水質報告

       System.out.println("當水質為正常的時候------------------〉");

       subject.setPolluteLevel(0);

        System.out.println("當水質為輕度汙染的時候---------------〉");

       subject.setPolluteLevel(1);

       System.out.println("當水質為中度汙染的時候---------------〉");

       subject.setPolluteLevel(2);

    }

}

(6)執行一下,看看結果,如下:

當水質為正常的時候------------------〉

監測人員獲取到通知,當前汙染級別為:0

當水質為輕度汙染的時候---------------〉

監測人員獲取到通知,當前汙染級別為:1

預警人員獲取到通知,當前汙染級別為:1

當水質為中度汙染的時候---------------〉

監測人員獲取到通知,當前汙染級別為:2

預警人員獲取到通知,當前汙染級別為:2

監測部門領導獲取到通知,當前汙染級別為:2

       仔細觀察上面輸出的結果,你會發現,當填寫不同的汙染級別時,被通知的人員是不同的。但是這些觀察者是不知道這些不同的,觀察者只是在自己獲得通知的時候去執行自己的工作。具體要不要通知,什麼時候通知都是目標物件的工作。      

12.3.8  相關模式

l          觀察者模式和狀態模式
    觀察者模式和狀態模式是有相似之處的。
    觀察者模式是當目標狀態發生改變時,觸發並通知觀察者,讓觀察者去執行相應的操作。而狀態模式是根據不同的狀態,選擇不同的實現,這個實現類的主要功能就 是針對狀態的相應的操作,它不像觀察者,觀察者本身還有很多其它的功能,接收通知並執行相應處理只是觀察者的部分功能。
    當然觀察者模式和狀態模式是可以結合使用的。觀察者模式的重心在觸發聯動,但是到底決定哪些觀察者會被聯動,這時就可以採用狀態模式來實現了,也可以採用策略模式來進行選擇需要聯動的觀察者。

l          觀察者模式和中介者模式
    觀察者模式和中介者模式是可以結合使用的。
    前面的例子中目標都只是簡單的通知一下,然後讓各個觀察者自己去完成更新就結束了。如果觀察者和被觀察的目標之間的互動關係很複雜,比如:有一個介面,裡 面有三個下拉列表元件,分別是選擇國家、省份/州、具體的城市,很明顯這是一個三級聯動,當你選擇一個國家的時候,省份/州應該相應改變資