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