來考慮實際生活中訂閱報紙的過程,這裏簡單總結了一下,訂閱報紙的基本流程以下:html
首先按照本身的須要選擇合適的報紙,具體的報刊雜誌目錄能夠從郵局獲取。java
選擇好後,就到郵局去填寫訂閱單,同時交上所需的費用。設計模式
至此,就完成了報紙的訂閱過程,接下去的就是耐心等候,報社會按照出報時間推出報紙,而後報紙會被送到每一個訂閱人的手裏。ide
畫個圖來描述上述過程,如圖12.1所示:測試
圖12.1 訂閱報紙的過程示意圖大數據
在上述過程當中,訂閱者在完成訂閱後,最關心的問題就是什麼時候能收到新出的報紙。幸虧在現實生活中,報紙都是按期出版,這樣發放到訂閱者手中也基本上有一個大體的時間範圍,差很少到時間了,訂閱者就會看看郵箱,查收新的報紙。this
要是報紙出版的時間不固定呢?spa
那訂閱者就麻煩了,若是訂閱者想要第一時間閱讀到新報紙,恐怕只能每天守着郵箱了,這未免也太痛苦了吧。設計
繼續引伸一下,用類來描述上述的過程,描述以下:orm
訂閱者類向出版者類訂閱報紙,很明顯不會只有一個訂閱者訂閱報紙,訂閱者類能夠有不少;當出版者類出版新報紙的時候,多個訂閱者類如何知道呢?還有訂閱者類如何獲得新報紙的內容呢?
把上面的問題對比描述一下:
進一步抽象描述這個問題:當一個對象的狀態發生改變的時候,如何讓依賴於它的全部對象獲得通知,並進行相應的處理呢?
該如何解決這樣的問題?
用來解決上述問題的一個合理的解決方案就是觀察者模式。那麼什麼是觀察者模式呢?
(1)觀察者模式定義
(2)應用觀察者模式來解決的思路
在前面描述的訂閱報紙的例子裏面,對於報社來講,在一開始,它並不清楚究竟有多少個訂閱者會來訂閱報紙,所以,報社須要維護一個訂閱者的列表,這樣當報社出版報紙的時候,纔可以把報紙發放到全部的訂閱者手中。對於訂閱者來講,訂閱者也就是看報的讀者,多個訂閱者會訂閱同一份報紙。
這就出現了一個典型的一對多的對象關係,一個報紙對象,會有多個訂閱者對象來訂閱;當報紙出版的時候,也就是報紙對象改變的時候,須要通知全部的訂閱者對象。那麼怎麼來創建並維護這樣的關係呢?
觀察者模式能夠處理這種問題,觀察者模式把這多個訂閱者稱爲觀察者:Observer,多個觀察者觀察的對象被稱爲目標:Subject。
一個目標能夠有任意多個觀察者對象,一旦目標的狀態發生了改變,全部註冊的觀察者都會獲得通知,而後各個觀察者會對通知做出相應的響應,執行相應的業務功能處理,並使本身的狀態和目標對象的狀態保持一致。
觀察者模式結構如圖12.3所示:
圖12.3 觀察者模式結構示意圖
Subject:
目標對象,一般具備以下功能:
一個目標能夠被多個觀察者觀察
目標提供對觀察者註冊和退訂的維護
當目標的狀態發生變化時,目標負責通知全部註冊的、有效的觀察者
Observer:
定義觀察者的接口,提供目標通知時對應的更新方法,這個更新方法進行相應的業務處理,能夠在這個方法裏面回調目標對象,以獲取目標對象的數據。
ConcreteSubject:
具體的目標實現對象,用來維護目標狀態,當目標對象的狀態發生改變時,通知全部註冊有效的觀察者,讓觀察者執行相應的處理。
ConcreteObserver:
觀察者的具體實現對象,用來接收目標的通知,並進行相應的後續處理,好比更新自身的狀態以保持和目標的相應狀態一致。
(1)先來看看目標對象的定義,示例代碼以下:
/** * 目標對象,它知道觀察它的觀察者,並提供註冊和刪除觀察者的接口 */ public class Subject { /** * 用來保存註冊的觀察者對象 */ private List<Observer> observers = new ArrayList<Observer>(); /** * 註冊觀察者對象 * @param observer 觀察者對象 */ public void attach(Observer observer) { observers.add(observer); } /** * 刪除觀察者對象 * @param observer 觀察者對象 */ public void detach(Observer observer) { observers.remove(observer); } /** * 通知全部註冊的觀察者對象 */ protected void notifyObservers() { for(Observer observer : observers){ observer.update(this); } } }
(2)接下來看看具體的目標對象,示例代碼以下:
/** * 具體的目標對象,負責把有關狀態存入到相應的觀察者對象, * 並在本身狀態發生改變時,通知各個觀察者 */ public class ConcreteSubject extends Subject { /** * 示意,目標對象的狀態 */ private String subjectState; public String getSubjectState() { return subjectState; } public void setSubjectState(String subjectState) { this.subjectState = subjectState; //狀態發生了改變,通知各個觀察者 this.notifyObservers(); } }
(3)再來看看觀察者的接口定義,示例代碼以下:
/** * 觀察者接口,定義一個更新的接口給那些在目標發生改變的時候被通知的對象 */ public interface Observer { /** * 更新的接口 * @param subject 傳入目標對象,好獲取相應的目標對象的狀態 */ public void update(Subject subject); }
(4)接下來看看觀察者的具體實現示意,示例代碼以下:
/** * 具體觀察者對象,實現更新的方法,使自身的狀態和目標的狀態保持一致 */ public class ConcreteObserver implements Observer { /** * 示意,觀者者的狀態 */ private String observerState; public void update(Subject subject) { // 具體的更新實現 //這裏可能須要更新觀察者的狀態,使其與目標的狀態保持一致 observerState = ((ConcreteSubject)subject) .getSubjectState(); } }
要使用觀察者模式來實現示例,那就按照前面講述的實現思路,把報紙對象看成目標,而後訂閱者當作觀察者,就能夠實現出來了。
使用觀察者模式來實現示例的結構如圖12.4所示:
仍是來看看具體的代碼實現。
(1)被觀察的目標
在前面描述的訂閱報紙的例子裏面,多個訂閱者都是在觀察同一個報社對象,這個報社對象就是被觀察的目標。這個目標的接口應該有些什麼方法呢?仍是從實際入手去想,看看報社都有些什麼功能。報社最基本有以下的功能:
註冊訂閱者,也就是說不少我的來訂報紙,報社確定要有相應的記錄才行
出版報紙,這個是報社的主要工做
發行報紙,也就是要把出版的報紙發送到訂閱者手中
退訂報紙,當訂閱者不想要繼續訂閱了,能夠取消訂閱
上面這些功能是報社最最基本的功能,固然,報社還有不少別的功能,爲了簡單起見,這裏就再也不去描述了。所以報社這個目標的接口也應該實現上述功能,把他們定義在目標接口裏面,示例代碼以下:
/** * 目標對象,做爲被觀察者 */ 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); } /** * 當每期報紙印刷出來後,就要迅速主動的被送到讀者的手中, * 至關於通知讀者,讓他們知道 */ protected void notifyObservers() { for(Observer reader : readers){ reader.update(this); } } }
細心的朋友可能會發現,這個對象並無定義出版報紙的功能,這是爲了讓這個對象更加通用,這個功能仍是有的,放到具體報紙類裏面去了,下面就來具體的看看具體的報紙類的實現。
爲了演示簡單,在這個實現類裏面增添一個屬性,用它來保存報紙的內容,而後增添一個方法來修改這個屬性,修改這個屬性就至關於出版了新的報紙,而且同時通知全部的訂閱者。示例代碼以下:
/** * 報紙對象,具體的目標實現 */ public class NewsPaper extends Subject{ /** * 報紙的具體內容 */ private String content; /** * 獲取報紙的具體內容 * @return 報紙的具體內容 */ public String getContent() { return content; } /** * 示意,設置報紙的具體內容,至關於要出版報紙了 * @param content 報紙的具體內容 */ public void setContent(String content) { this.content = content; //內容有了,說明又出報紙了,那就通知全部的讀者 notifyObservers(); } }
(2)觀察者
目標定義好事後,接下來把觀察者抽象出來,看看它應該具備什麼功能。分析前面的描述,發現觀察者只要去郵局註冊了事後,就是等着接收報紙就行了,沒有什麼其它的功能。那麼就把這個接收報紙的功能抽象成爲更新的方法,從而定義出觀察者接口來,示例代碼以下:
/** * 觀察者,好比報紙的讀者 */ public interface Observer { /** * 被通知的方法 * @param subject 具體的目標對象,能夠獲取報紙的內容 */ public void update(Subject subject); }
定義好了觀察者的接口事後,該來想一想如何實現了。具體的觀察者須要實現:在收到被通知的內容後,自身如何進行相應處理的功能。爲了演示的簡單,收到報紙內容事後,簡單的輸出一下,表示收到了就好了。
定義一個簡單的觀察者實現,示例代碼以下:
/** * 真正的讀者,爲了簡單就描述一下姓名 */ public class Reader implements Observer{ /** * 讀者的姓名 */ private String name; public void update(Subject subject) { //這是採用拉的方式 System.out.println(name+"收到報紙了,閱讀先。內容是===" +((NewsPaper)subject).getContent()); } public String getName() { return name; } public void setName(String name) { this.name = name; } }
(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.attach(reader1); subject.attach(reader2); subject.attach(reader3); //要出報紙啦 subject.setContent("本期內容是觀察者模式"); } }
測試一下看看,輸出結果以下:
張三收到報紙了,閱讀先。內容是===本期內容是觀察者模式 李四收到報紙了,閱讀先。內容是===本期內容是觀察者模式 王五收到報紙了,閱讀先。內容是===本期內容是觀察者模式
你還能夠經過改變註冊的觀察者,或者是註冊了又退訂,來看看輸出的結果。會發現沒有註冊或者退訂的觀察者是收不到報紙的。
如同前面的示例,讀者和報社是一種典型的一對多的關係,一個報社有多個讀者,當報社的狀態發生改變,也就是出版新報紙的時候,全部註冊的讀者都會獲得通知,而後讀者會拿到報紙,讀者會去閱讀報紙並進行後續的操做。
(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所示:
(8)通知的順序
從理論上說,當目標對象的狀態變化後通知全部觀察者的時候,順序是不肯定的,所以觀察者實現的功能,絕對不要依賴於通知的順序,也就是說,多個觀察者之間的功能是平行的,相互不該該有前後的依賴關係。
在觀察者模式的實現裏面,又分爲推模型和拉模型兩種方式,什麼意思呢?
推模型
目標對象主動向觀察者推送目標的詳細信息,無論觀察者是否須要,推送的信息一般是目標對象的所有或部分數據,至關因而在廣播通訊。
拉模型
目標對象在通知觀察者的時候,只傳遞少許信息,若是觀察者須要更具體的信息,由觀察者主動到目標對象中獲取,至關因而觀察者從目標對象中拉數據。
通常這種模型的實現中,會把目標對象自身經過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方法的參數是目標對象自己,這基本上是目標對象能傳遞的最大數據集合了,基本上能夠適應各類狀況的須要。
估計有些朋友在看前面的內容的時候,內心就嘀咕上了,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已有的功能來實現觀察者模式,看看有什麼不一樣,有什麼相同,好好體會一下。
l 觀察者模式實現了觀察者和目標之間的抽象耦合
本來目標對象在狀態發生改變的時候,須要直接調用全部的觀察者對象,可是抽象出觀察者接口事後,目標和觀察者就只是在抽象層面上耦合了,也就是說目標只是知道觀察者接口,並不知道具體的觀察者的類,從而實現目標類和具體的觀察者類之間解耦。
l 觀察者模式實現了動態聯動
所謂聯動,就是作一個操做會引發其它相關的操做。因爲觀察者模式對觀察者註冊實行管理,那就能夠在運行期間,經過動態的控制註冊的觀察者,來控制某個動做的聯動範圍,從而實現動態聯動。
l 觀察者模式支持廣播通訊
因爲目標發送通知給觀察者是面向全部註冊的觀察者,因此每次目標通知的信息就要對全部註冊的觀察者進行廣播。固然,也能夠經過在目標上添加新的功能來限制廣播的範圍。
在廣播通訊的時候要注意一個問題,就是相互廣播形成死循環的問題。好比A和B兩個對象互爲觀察者和目標對象,A對象發生狀態變化,而後A來廣播信息,B對象接收到通知後,在處理過程當中,使得B對象的狀態也發生了改變,而後B來廣播信息,而後A對象接到通知後,又觸發廣播信息……,如此A引發B變化,B又引發A變化,從而一直相互廣播信息,就形成死循環了。
l 觀察者模式可能會引發無謂的操做
因爲觀察者模式每次都是廣播通訊,無論觀察者需不須要,每一個觀察者都會被調用update方法,若是觀察者不須要執行相應處理,那麼此次操做就浪費了。
其實浪費了還好,怕就怕引發了誤更新,那就麻煩了,好比:本應該在執行此次狀態更新前把某個觀察者刪除掉,這樣通知的時候就沒有這個觀察者了,可是如今忘掉了,那麼就會引發誤操做。
1:觀察者模式的本質
觀察者模式的本質:觸發聯動。
當修改目標對象的狀態的時候,就會觸發相應的通知,而後會循環調用全部註冊的觀察者對象的相應方法,其實就至關於聯動調用這些觀察者的方法。
並且這個聯動仍是動態的,能夠經過註冊和取消註冊來控制觀察者,於是能夠在程序運行期間,經過動態的控制觀察者,來變相的實現添加和刪除某些功能處理,這些功能就是觀察者在update的時候執行的功能。
同時目標對象和觀察者對象的解耦,又保證了不管觀察者發生怎樣的變化,目標對象老是可以正確地聯動過來。
理解這個本質對咱們很是有用,對於咱們識別和使用觀察者模式有很是重要的意義,尤爲是在變形使用的時候,萬變不離其宗。
2:什麼時候選用觀察者模式
建議在以下狀況中,選用觀察者模式:
當一個抽象模型有兩個方面,其中一個方面的操做依賴於另外一個方面的狀態變化,那麼就能夠選用觀察者模式,將這二者封裝成觀察者和目標對象,當目標對象變化的時候,依賴於它的觀察者對象也會發生相應的變化。這樣就把抽象模型的這兩個方面分離開了,使得它們能夠獨立的改變和複用。
若是在更改一個對象的時候,須要同時連帶改變其它的對象,並且不知道究竟應該有多少對象須要被連帶改變,這種狀況能夠選用觀察者模式,被更改的那一個對象很明顯就至關因而目標對象,而須要連帶修改的多個其它對象,就做爲多個觀察者對象了。
當一個對象必須通知其它的對象,可是你又但願這個對象和其它被它通知的對象是鬆散耦合的,也就是說這個對象其實不想知道具體被通知的對象,這種狀況能夠選用觀察者模式,這個對象就至關因而目標對象,而被它通知的對象就是觀察者對象了。
Java的Swing中處處都是觀察者模式的身影,好比你們熟悉的事件處理,就是典型的觀察者模式的應用。(說明一下:早期的Swing事件處理用的是職責鏈)
Swing組件是被觀察的目標,而每一個實現監聽器的類就是觀察者,監聽器的接口就是觀察者的接口,在調用addXXXListener方法的時候就至關於註冊觀察者。
當組件被點擊,狀態發生改變的時候,就會產生相應的通知,會調用註冊的觀察者的方法,就是咱們所實現的監聽器的方法。
從這裏還能夠學一招:如何處理一個觀察者觀察多個目標對象?
你看一個Swing的應用程序,做爲一個觀察者,常常會註冊觀察多個不一樣的目標對象,也就是同一類,既實現了按鈕組件的事件處理,又實現了文本框組件的事件處理,是怎麼作到的呢?
答案就在監聽器接口上,這些監聽器接口就至關於觀察者接口,也就是說一個觀察者要觀察多個目標對象,只要不一樣的目標對象使用不一樣的觀察者接口就行了,固然,這些接口裏面的方法也不相同,再也不都是update方法了。這樣一來,不一樣的目標對象通知觀察者所調用的方法也就不一樣了,這樣在具體實現觀察者的時候,也就實現成不一樣的方法,天然就區分開了。
首先聲明,這裏只是舉一個很是簡單的變形使用的例子,也可算是基本的觀察者模式的功能增強,事實上能夠有不少不少的變形應用,這也是爲何咱們特別強調你們要深刻理解每一個設計模式,要把握每一個模式的本質的緣由了。
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
仔細觀察上面輸出的結果,你會發現,當填寫不一樣的污染級別時,被通知的人員是不一樣的。可是這些觀察者是不知道這些不一樣的,觀察者只是在本身得到通知的時候去執行本身的工做。具體要不要通知,何時通知都是目標對象的工做。
l 觀察者模式和狀態模式
觀察者模式和狀態模式是有類似之處的。
觀察者模式是當目標狀態發生改變時,觸發並通知觀察者,讓觀察者去執行相應的操做。而狀態模式是根據不一樣的狀態,選擇不一樣的實現,這個實現類的主要功能就是針對狀態的相應的操做,它不像觀察者,觀察者自己還有不少其它的功能,接收通知並執行相應處理只是觀察者的部分功能。
固然觀察者模式和狀態模式是能夠結合使用的。觀察者模式的重心在觸發聯動,可是到底決定哪些觀察者會被聯動,這時就能夠採用狀態模式來實現了,也能夠採用策略模式來進行選擇須要聯動的觀察者。
l 觀察者模式和中介者模式
觀察者模式和中介者模式是能夠結合使用的。
前面的例子中目標都只是簡單的通知一下,而後讓各個觀察者本身去完成更新就結束了。若是觀察者和被觀察的目標之間的交互關係很複雜,好比:有一個界面,裏面有三個下拉列表組件,分別是選擇國家、省份/州、具體的城市,很明顯這是一個三級聯動,當你選擇一個國家的時候,省份/州應該相應改變數據,省份/州一改變,具體的城市也須要改變。
這種狀況下,很明顯須要相關的狀態都聯動準備好了,而後再一次性的通知觀察者,就是界面作更新處理,不會國家改變一下,省份和城市尚未改,就通知界面更新。這種狀況就可使用中介者模式來封裝觀察者和目標的關係。
在使用Swing的小型應用裏面,也可使用中介者模式。好比:把一個界面全部的事件用一個對象來處理,把一個組件觸發事件事後,須要操做其它組件的動做都封裝到一塊兒,這個對象就是典型的中介者。
轉載至:http://sishuok.com/forum/blogPost/list/5281.html
cc老師的設計模式是我目前看過最詳細最有實踐的教程。