对象间的联动——观察者模式
本文轉載自 :http://blog.csdn.net/lovelion/article/details/7720232
觀察者模式是設計模式中的“超級模式”,其應用隨處可見,在之后幾篇文章里,我將向大家詳細介紹觀察者模式。
?
????? “紅燈停,綠燈行”,在日常生活中,交通信號燈裝點著我們的城市,指揮著日益擁擠的城市交通。當紅燈亮起,來往的汽車將停止;而綠燈亮起,汽車可以繼續(xù)前行。在這個過程中,交通信號燈是汽車(更準確地說應該是汽車駕駛員)的觀察目標,而汽車是觀察者。隨著交通信號燈的變化,汽車的行為也將隨之而變化,一盞交通信號燈可以指揮多輛汽車。如圖22-1所示:
圖22-1??交通信號燈與汽車示意圖
【插曲:本圖是根據(jù)網絡上一張圖PS的,但改為黑白圖片之后我把那張彩色的原始圖片刪除了,后悔ing...,怎么根據(jù)黑白圖片查詢彩色圖片啊,這樣的工具,有木有!!那張彩色的原圖很漂亮,有木有人能夠幫我找一找,】
????? 在軟件系統(tǒng)中,有些對象之間也存在類似交通信號燈和汽車之間的關系,一個對象的狀態(tài)或行為的變化將導致其他對象的狀態(tài)或行為也發(fā)生改變,它們之間將產生聯(lián)動,正所謂“觸一而牽百發(fā)”。為了更好地描述對象之間存在的這種一對多(包括一對一)的聯(lián)動,觀察者模式應運而生,它定義了對象之間一種一對多的依賴關系,讓一個對象的改變能夠影響其他對象。本章我們將學習用于實現(xiàn)對象間聯(lián)動的觀察者模式。
22.1? 多人聯(lián)機對戰(zhàn)游戲的設計
| ????? Sunny軟件公司欲開發(fā)一款多人聯(lián)機對戰(zhàn)游戲(類似魔獸世界、星際爭霸等游戲),在該游戲中,多個玩家可以加入同一戰(zhàn)隊組成聯(lián)盟,當戰(zhàn)隊中某一成員受到敵人攻擊時將給所有其他盟友發(fā)送通知,盟友收到通知后將作出響應。 ????? Sunny軟件公司開發(fā)人員需要提供一個設計方案來實現(xiàn)戰(zhàn)隊成員之間的聯(lián)動。 |
?????? Sunny軟件公司開發(fā)人員通過對系統(tǒng)功能需求進行分析,發(fā)現(xiàn)在該系統(tǒng)中戰(zhàn)隊成員之間的聯(lián)動過程可以簡單描述如下:
??????聯(lián)盟成員受到攻擊-->發(fā)送通知給盟友-->盟友作出響應。
????? 如果按照上述思路來設計系統(tǒng),由于聯(lián)盟成員在受到攻擊時需要通知他的每一個盟友,因此每個聯(lián)盟成員都需要持有其他所有盟友的信息,這將導致系統(tǒng)開銷較大,因此Sunny公司開發(fā)人員決定引入一個新的角色——“戰(zhàn)隊控制中心”——來負責維護和管理每個戰(zhàn)隊所有成員的信息。當一個聯(lián)盟成員受到攻擊時,將向相應的戰(zhàn)隊控制中心發(fā)送求助信息,戰(zhàn)隊控制中心再逐一通知每個盟友,盟友再作出響應,如圖22-2所示:
圖22-2? ?多人聯(lián)機對戰(zhàn)游戲中對象的聯(lián)動
???????在圖22-2中,受攻擊的聯(lián)盟成員將與戰(zhàn)隊控制中心產生聯(lián)動,戰(zhàn)隊控制中心還將與其他盟友產生聯(lián)動。
???????如何實現(xiàn)對象之間的聯(lián)動?如何讓一個對象的狀態(tài)或行為改變時,依賴于它的對象能夠得到通知并進行相應的處理?
???????別著急,本章所介紹的觀察者模式將為對象之間的聯(lián)動提供一個優(yōu)秀的解決方案,下面就讓我們正式進入觀察者模式的學習。
22.2? 觀察者模式概述
????? 觀察者模式是使用頻率最高的設計模式之一,它用于建立一種對象與對象之間的依賴關系,一個對象發(fā)生改變時將自動通知其他對象,其他對象將相應作出反應。在觀察者模式中,發(fā)生改變的對象稱為觀察目標,而被通知的對象稱為觀察者,一個觀察目標可以對應多個觀察者,而且這些觀察者之間可以沒有任何相互聯(lián)系,可以根據(jù)需要增加和刪除觀察者,使得系統(tǒng)更易于擴展。
??????觀察者模式定義如下:
| 觀察者模式(Observer Pattern):定義對象之間的一種一對多依賴關系,使得每當一個對象狀態(tài)發(fā)生改變時,其相關依賴對象皆得到通知并被自動更新。觀察者模式的別名包括發(fā)布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監(jiān)聽器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式是一種對象行為型模式。 |
????? 觀察者模式結構中通常包括觀察目標和觀察者兩個繼承層次結構,其結構如圖22-3所示:
圖22-3 ?觀察者模式結構圖
??????在觀察者模式結構圖中包含如下幾個角色:
????? ●?Subject(目標):目標又稱為主題,它是指被觀察的對象。在目標中定義了一個觀察者集合,一個觀察目標可以接受任意數(shù)量的觀察者來觀察,它提供一系列方法來增加和刪除觀察者對象,同時它定義了通知方法notify()。目標類可以是接口,也可以是抽象類或具體類。
????? ●?ConcreteSubject(具體目標):具體目標是目標類的子類,通常它包含有經常發(fā)生改變的數(shù)據(jù),當它的狀態(tài)發(fā)生改變時,向它的各個觀察者發(fā)出通知;同時它還實現(xiàn)了在目標類中定義的抽象業(yè)務邏輯方法(如果有的話)。如果無須擴展目標類,則具體目標類可以省略。
????? ●?Observer(觀察者):觀察者將對觀察目標的改變做出反應,觀察者一般定義為接口,該接口聲明了更新數(shù)據(jù)的方法update(),因此又稱為抽象觀察者。
????? ●?ConcreteObserver(具體觀察者):在具體觀察者中維護一個指向具體目標對象的引用,它存儲具體觀察者的有關狀態(tài),這些狀態(tài)需要和具體目標的狀態(tài)保持一致;它實現(xiàn)了在抽象觀察者Observer中定義的update()方法。通常在實現(xiàn)時,可以調用具體目標類的attach()方法將自己添加到目標類的集合中或通過detach()方法將自己從目標類的集合中刪除。
??????觀察者模式描述了如何建立對象與對象之間的依賴關系,以及如何構造滿足這種需求的系統(tǒng)。觀察者模式包含觀察目標和觀察者兩類對象,一個目標可以有任意數(shù)目的與之相依賴的觀察者,一旦觀察目標的狀態(tài)發(fā)生改變,所有的觀察者都將得到通知。作為對這個通知的響應,每個觀察者都將監(jiān)視觀察目標的狀態(tài)以使其狀態(tài)與目標狀態(tài)同步,這種交互也稱為發(fā)布-訂閱(Publish-Subscribe)。觀察目標是通知的發(fā)布者,它發(fā)出通知時并不需要知道誰是它的觀察者,可以有任意數(shù)目的觀察者訂閱它并接收通知。
????? 下面通過示意代碼來對該模式進行進一步分析。首先我們定義一個抽象目標Subject,典型代碼如下所示:
[java]?view plaincopy????? 具體目標類ConcreteSubject是實現(xiàn)了抽象目標類Subject的一個具體子類,其典型代碼如下所示:
[java]?view plaincopy????? 抽象觀察者角色一般定義為一個接口,通常只聲明一個update()方法,為不同觀察者的更新(響應)行為定義相同的接口,這個方法在其子類中實現(xiàn),不同的觀察者具有不同的響應方法。抽象觀察者Observer典型代碼如下所示:
[java]?view plaincopy????? 在具體觀察者ConcreteObserver中實現(xiàn)了update()方法,其典型代碼如下所示:
[java]?view plaincopy????? 在有些更加復雜的情況下,具體觀察者類ConcreteObserver的update()方法在執(zhí)行時需要使用到具體目標類ConcreteSubject中的狀態(tài)(屬性),因此在ConcreteObserver與ConcreteSubject之間有時候還存在關聯(lián)或依賴關系,在ConcreteObserver中定義一個ConcreteSubject實例,通過該實例獲取存儲在ConcreteSubject中的狀態(tài)。如果ConcreteObserver的update()方法不需要使用到ConcreteSubject中的狀態(tài)屬性,則可以對觀察者模式的標準結構進行簡化,在具體觀察者ConcreteObserver和具體目標ConcreteSubject之間無須維持對象引用。如果在具體層具有關聯(lián)關系,系統(tǒng)的擴展性將受到一定的影響,增加新的具體目標類有時候需要修改原有觀察者的代碼,在一定程度上違反了“開閉原則”,但是如果原有觀察者類無須關聯(lián)新增的具體目標,則系統(tǒng)擴展性不受影響。
?
|
23.3 完整解決方案
????? 為了實現(xiàn)對象之間的聯(lián)動,Sunny軟件公司開發(fā)人員決定使用觀察者模式來進行多人聯(lián)機對戰(zhàn)游戲的設計,其基本結構如圖22-4所示:
圖22-4??多人聯(lián)機對戰(zhàn)游戲結構圖
????? 在圖22-4中,AllyControlCenter充當目標類,ConcreteAllyControlCenter充當具體目標類,Observer充當抽象觀察者,Player充當具體觀察者。完整代碼如下所示:
[java]?view plaincopy????? 編寫如下客戶端測試代碼:
[java]?view plaincopy????? 編譯并運行程序,輸出結果如下:
| 金庸群俠戰(zhàn)隊組建成功! ---------------------------- 楊過加入金庸群俠戰(zhàn)隊! 令狐沖加入金庸群俠戰(zhàn)隊! 張無忌加入金庸群俠戰(zhàn)隊! 段譽加入金庸群俠戰(zhàn)隊! 楊過被攻擊! 金庸群俠戰(zhàn)隊緊急通知,盟友楊過遭受敵人攻擊! 堅持住,令狐沖來救你! 堅持住,張無忌來救你! 堅持住,段譽來救你! |
????? 在本實例中,實現(xiàn)了兩次對象之間的聯(lián)動,當一個游戲玩家Player對象的beAttacked()方法被調用時,將調用AllyControlCenter的notifyObserver()方法來進行處理,而在notifyObserver()方法中又將調用其他Player對象的help()方法。Player的beAttacked()方法、AllyControlCenter的notifyObserver()方法以及Player的help()方法構成了一個聯(lián)動觸發(fā)鏈,執(zhí)行順序如下所示:
Player.beAttacked()?-->?AllyControlCenter.notifyObserver() -->Player.help()。
22.4 JDK對觀察者模式的支持
????? 觀察者模式在Java語言中的地位非常重要。在JDK的java.util包中,提供了Observable類以及Observer接口,它們構成了JDK對觀察者模式的支持。如圖22-5所示:
圖22-5 JDK提供的Observable類及Observer接口結構圖
????? (1) ?Observer接口
????? 在java.util.Observer接口中只聲明一個方法,它充當抽象觀察者,其方法聲明代碼如下所示:
| void? update(Observable o, Object arg); |
????? 當觀察目標的狀態(tài)發(fā)生變化時,該方法將會被調用,在Observer的子類中將實現(xiàn)update()方法,即具體觀察者可以根據(jù)需要具有不同的更新行為。當調用觀察目標類Observable的notifyObservers()方法時,將執(zhí)行觀察者類中的update()方法。
????? (2)? Observable類
????? java.util.Observable類充當觀察目標類,在Observable中定義了一個向量Vector來存儲觀察者對象,它所包含的方法及說明見表22-1:
表22-1 Observable類所包含方法及說明
| 方法名 | 方法描述 |
| Observable() | 構造方法,實例化Vector向量。 |
| addObserver(Observer? o) | 用于注冊新的觀察者對象到向量中。 |
| deleteObserver? (Observer o) | 用于刪除向量中的某一個觀察者對象。 |
| notifyObservers()和notifyObservers(Object arg) | 通知方法,用于在方法內部循環(huán)調用向量中每一個觀察者的update()方法。 |
| deleteObservers() | 用于清空向量,即刪除向量中所有觀察者對象。 |
| setChanged() | 該方法被調用后會設置一個boolean類型的內部標記變量changed的值為true,表示觀察目標對象的狀態(tài)發(fā)生了變化。 |
| clearChanged() | 用于將changed變量的值設為false,表示對象狀態(tài)不再發(fā)生改變或者已經通知了所有的觀察者對象,調用了它們的update()方法。 |
| hasChanged() | 用于測試對象狀態(tài)是否改變。 |
| countObservers() | 用于返回向量中觀察者的數(shù)量。 |
????? 我們可以直接使用Observer接口和Observable類來作為觀察者模式的抽象層,再自定義具體觀察者類和具體觀察目標類,通過使用JDK中的Observer接口和Observable類,可以更加方便地在Java語言中應用觀察者模式。
22.5 觀察者模式與Java事件處理
?????? JDK 1.0及更早版本的事件模型基于職責鏈模式,但是這種模型不適用于復雜的系統(tǒng),因此在JDK 1.1及以后的各個版本中,事件處理模型采用基于觀察者模式的委派事件模型(DelegationEvent Model, DEM),即一個Java組件所引發(fā)的事件并不由引發(fā)事件的對象自己來負責處理,而是委派給獨立的事件處理對象負責。
????? 在DEM模型中,目標角色(如界面組件)負責發(fā)布事件,而觀察者角色(事件處理者)可以向目標訂閱它所感興趣的事件。當一個具體目標產生一個事件時,它將通知所有訂閱者。事件的發(fā)布者稱為事件源(Event Source),而訂閱者稱為事件監(jiān)聽器(Event Listener),在這個過程中還可以通過事件對象(Event Object)來傳遞與事件相關的信息,可以在事件監(jiān)聽者的實現(xiàn)類中實現(xiàn)事件處理,因此事件監(jiān)聽對象又可以稱為事件處理對象。事件源對象、事件監(jiān)聽對象(事件處理對象)和事件對象構成了Java事件處理模型的三要素。事件源對象充當觀察目標,而事件監(jiān)聽對象充當觀察者。以按鈕點擊事件為例,其事件處理流程如下:
?????? (1)?如果用戶在GUI中單擊一個按鈕,將觸發(fā)一個事件(如ActionEvent類型的動作事件),JVM將產生一個相應的ActionEvent類型的事件對象,在該事件對象中包含了有關事件和事件源的信息,此時按鈕是事件源對象;
?????? (2)?將ActionEvent事件對象傳遞給事件監(jiān)聽對象(事件處理對象),JDK提供了專門用于處理ActionEvent事件的接口ActionListener,開發(fā)人員需提供一個ActionListener的實現(xiàn)類(如MyActionHandler),實現(xiàn)在ActionListener接口中聲明的抽象事件處理方法actionPerformed(),對所發(fā)生事件做出相應的處理;
?????? (3)?開發(fā)人員將ActionListener接口的實現(xiàn)類(如MyActionHandler)對象注冊到按鈕中,可以通過按鈕類的addActionListener()方法來實現(xiàn)注冊;
?????? (4) JVM在觸發(fā)事件時將調用按鈕的fireXXX()方法,在該方法內部將調用注冊到按鈕中的事件處理對象的actionPerformed()方法,實現(xiàn)對事件的處理。
???????使用類似的方法,我們可自定義GUI組件,如包含兩個文本框和兩個按鈕的登錄組件LoginBean,可以采用如圖22-6所示設計方案:
圖22-6?自定義登錄組件結構圖【省略按鈕、文本框等界面組件】
?????? 圖22-6中相關類說明如下:
?????? (1) LoginEvent是事件類,它用于封裝與事件有關的信息,它不是觀察者模式的一部分,但是它可以在目標對象和觀察者對象之間傳遞數(shù)據(jù),在AWT事件模型中,所有的自定義事件類都是java.util.EventObject的子類。
???????(2) LoginEventListener充當抽象觀察者,它聲明了事件響應方法validateLogin(),用于處理事件,該方法也稱為事件處理方法,validateLogin()方法將一個LoginEvent類型的事件對象作為參數(shù),用于傳輸與事件相關的數(shù)據(jù),在其子類中實現(xiàn)該方法,實現(xiàn)具體的事件處理。
?????? (3) LoginBean充當具體目標類,在這里我們沒有定義抽象目標類,對觀察者模式進行了一定的簡化。在LoginBean中定義了抽象觀察者LoginEventListener類型的對象lel和事件對象LoginEvent,提供了注冊方法addLoginEventListener()用于添加觀察者,在Java事件處理中,通常使用的是一對一的觀察者模式,而不是一對多的觀察者模式,也就是說,一個觀察目標中只定義一個觀察者對象,而不是提供一個觀察者對象的集合。在LoginBean中還定義了通知方法fireLoginEvent(),該方法在Java事件處理模型中稱為“點火方法”,在該方法內部實例化了一個事件對象LoginEvent,將用戶輸入的信息傳給觀察者對象,并且調用了觀察者對象的響應方法validateLogin()。
????? (4) LoginValidatorA和LoginValidatorB充當具體觀察者類,它們實現(xiàn)了在LoginEventListener接口中聲明的抽象方法validateLogin(),用于具體實現(xiàn)事件處理,該方法包含一個LoginEvent類型的參數(shù),在LoginValidatorA和LoginValidatorB類中可以針對相同的事件提供不同的實現(xiàn)。
22.6 觀察者模式與MVC
????? 在當前流行的MVC(Model-View-Controller)架構中也應用了觀察者模式,MVC是一種架構模式,它包含三個角色:模型(Model),視圖(View)和控制器(Controller)。其中模型可對應于觀察者模式中的觀察目標,而視圖對應于觀察者,控制器可充當兩者之間的中介者。當模型層的數(shù)據(jù)發(fā)生改變時,視圖層將自動改變其顯示內容。如圖22-7所示:
圖22-7 MVC結構示意圖
????? 在圖22-7中,模型層提供的數(shù)據(jù)是視圖層所觀察的對象,在視圖層中包含兩個用于顯示數(shù)據(jù)的圖表對象,一個是柱狀圖,一個是餅狀圖,相同的數(shù)據(jù)擁有不同的圖表顯示方式,如果模型層的數(shù)據(jù)發(fā)生改變,兩個圖表對象將隨之發(fā)生變化,這意味著圖表對象依賴模型層提供的數(shù)據(jù)對象,因此數(shù)據(jù)對象的任何狀態(tài)改變都應立即通知它們。同時,這兩個圖表之間相互獨立,不存在任何聯(lián)系,而且圖表對象的個數(shù)沒有任何限制,用戶可以根據(jù)需要再增加新的圖表對象,如折線圖。在增加新的圖表對象時,無須修改原有類庫,滿足“開閉原則”。
|
22.7 觀察者模式總結
????? 觀察者模式是一種使用頻率非常高的設計模式,無論是移動應用、Web應用或者桌面應用,觀察者模式幾乎無處不在,它為實現(xiàn)對象之間的聯(lián)動提供了一套完整的解決方案,凡是涉及到一對一或者一對多的對象交互場景都可以使用觀察者模式。觀察者模式廣泛應用于各種編程語言的GUI事件處理的實現(xiàn),在基于事件的XML解析技術(如SAX2)以及Web事件處理中也都使用了觀察者模式。
????? 1.主要優(yōu)點
????? 觀察者模式的主要優(yōu)點如下:
????? (1)?觀察者模式可以實現(xiàn)表示層和數(shù)據(jù)邏輯層的分離,定義了穩(wěn)定的消息更新傳遞機制,并抽象了更新接口,使得可以有各種各樣不同的表示層充當具體觀察者角色。
????? (2)?觀察者模式在觀察目標和觀察者之間建立一個抽象的耦合。觀察目標只需要維持一個抽象觀察者的集合,無須了解其具體觀察者。由于觀察目標和觀察者沒有緊密地耦合在一起,因此它們可以屬于不同的抽象化層次。
????? (3)?觀察者模式支持廣播通信,觀察目標會向所有已注冊的觀察者對象發(fā)送通知,簡化了一對多系統(tǒng)設計的難度。
????? (4)?觀察者模式滿足“開閉原則”的要求,增加新的具體觀察者無須修改原有系統(tǒng)代碼,在具體觀察者與觀察目標之間不存在關聯(lián)關系的情況下,增加新的觀察目標也很方便。
????? 2.主要缺點
????? 觀察者模式的主要缺點如下:
????? (1)?如果一個觀察目標對象有很多直接和間接觀察者,將所有的觀察者都通知到會花費很多時間。
????? (2)?如果在觀察者和觀察目標之間存在循環(huán)依賴,觀察目標會觸發(fā)它們之間進行循環(huán)調用,可能導致系統(tǒng)崩潰。
????? (3)?觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎么發(fā)生變化的,而僅僅只是知道觀察目標發(fā)生了變化。
????? 3.適用場景
????? 在以下情況下可以考慮使用觀察者模式:
????? (1)?一個抽象模型有兩個方面,其中一個方面依賴于另一個方面,將這兩個方面封裝在獨立的對象中使它們可以各自獨立地改變和復用。
????? (2)?一個對象的改變將導致一個或多個其他對象也發(fā)生改變,而并不知道具體有多少對象將發(fā)生改變,也不知道這些對象是誰。
????? (3)?需要在系統(tǒng)中創(chuàng)建一個觸發(fā)鏈,A對象的行為將影響B對象,B對象的行為將影響C對象……,可以使用觀察者模式創(chuàng)建一種鏈式觸發(fā)機制。
|
總結
以上是生活随笔為你收集整理的对象间的联动——观察者模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 算法的封装与切换——策略模式
- 下一篇: 自定义语言的实现——解释器模式