標籤:抽象 number 餅狀圖 .net 使用者輸入 觸發事件 運行 執行 公司
概述
它用於建立一種對象與對象之間的依賴關係,一個對象發生改變時將自動通知其他對象,其他對象將相應作出反應。在觀察者模式中,發生改變的對象稱為觀察目標,而被通知的對象稱為觀察者,一個觀察目標可以對應多個觀察者,而且這些觀察者之間可以沒有任何相互聯絡,可以根據需要增加和刪除觀察者,使得系統更易於擴充。
觀察者模式(Observer Pattern):定義對象之間的一種一對多依賴關係,使得每當一個對象狀態發生改變時,其相關依賴對象皆得到通知並被自動更新。觀察者模式的別名包括髮布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式是一種對象行為型模式。
觀察者模式結構
觀察者模式結構中通常包括觀察目標和觀察者兩個繼承階層,其結構:
在觀察者模式結構圖中包含如下幾個角色:
● Subject(目標):目標又稱為主題,它是指被觀察的對象。在目標中定義了一個觀察者集合,一個觀察目標可以接受任意數量的觀察者來觀察,它提供一系列方法來增加和刪除觀察者對象,同時它定義了通知方法notify()。目標類可以是介面,也可以是抽象類別或具體類。
● ConcreteSubject(具體目標):具體目標是目標類的子類,通常它包含有經常發生改變的資料,當它的狀態發生改變時,向它的各個觀察者發出通知;同時它還實現了在目標類中定義的抽象商務邏輯方法(如果有的話)。如果無須擴充目標類,則具體目標類可以省略。
● Observer(觀察者):觀察者將對觀察目標的改變做出反應,觀察者一般定義為介面,該介面聲明了更新資料的方法update(),因此又稱為抽象觀察者。
● ConcreteObserver(具體觀察者):在具體觀察者中維護一個指向具體目標對象的引用,它儲存具體觀察者的有關狀態,這些狀態需要和具體目標的狀態保持一致;它實現了在抽象觀察者Observer中定義的update()方法。通常在實現時,可以調用具體目標類的attach()方法將自己添加到目標類的集合中或通過detach()方法將自己從目標類的集合中刪除。
觀察者模式描述了如何建立對象與對象之間的依賴關係,以及如何構造滿足這種需求的系統。觀察者模式包含觀察目標和觀察者兩類對象,一個目標可以有任意數目的與之相依賴的觀察者,一旦觀察目標的狀態發生改變,所有的觀察者都將得到通知。作為對這個通知的響應,每個觀察者都將監視觀察目標的狀態以使其狀態與目標狀態同步,這種互動也稱為發布-訂閱(Publish-Subscribe)。觀察目標是通知的發行者,它發出通知時並不需要知道誰是它的觀察者,可以有任意數目的觀察者訂閱它並接收通知。
觀察者模式典型代碼
下面通過示意代碼來對該模式進行進一步分析。首先我們定義一個抽象目標Subject,典型代碼如下所示
import java.util.*; abstract class Subject { //定義一個觀察者集合用於儲存所有觀察者對象 protected ArrayList observers<Observer> = new ArrayList(); //註冊方法,用於向觀察者集合中增加一個觀察者 public void attach(Observer observer) { observers.add(observer); } //登出方法,用於在觀察者集合中刪除一個觀察者 public void detach(Observer observer) { observers.remove(observer); } //聲明抽象通知方法 public abstract void notify(); }
具體目標類ConcreteSubject是實現了抽象目標類Subject的一個具體子類,其典型代碼如下所示:
class ConcreteSubject extends Subject { //實現通知方法 public void notify() { //遍曆觀察者集合,調用每一個觀察者的回應程式法 for(Object obs:observers) { ((Observer)obs).update(); } } }
抽象觀察者角色一般定義為一個介面,通常只聲明一個update()方法,為不同觀察者的更新(響應)行為定義相同的介面,這個方法在其子類中實現,不同的觀察者具有不同的回應程式法。抽象觀察者Observer典型代碼如下所示:
interface Observer { //聲明回應程式法 public void update(); }
在具體觀察者ConcreteObserver中實現了update()方法,其典型代碼如下所示:
class ConcreteObserver implements Observer { //實現回應程式法 public void update() { //具體響應代碼 } }
在有些更加複雜的情況下,具體觀察者類ConcreteObserver的update()方法在執行時需要使用到具體目標類ConcreteSubject中的狀態(屬性),因此在ConcreteObserver與ConcreteSubject之間有時候還存在關聯或依賴關係,在ConcreteObserver中定義一個ConcreteSubject執行個體,通過該執行個體擷取儲存在ConcreteSubject中的狀態。如果ConcreteObserver的update()方法不需要使用到ConcreteSubject中的狀態屬性,則可以對觀察者模式的標準結構進行簡化,在具體觀察者ConcreteObserver和具體目標ConcreteSubject之間無須維持對象引用。如果在具體層具有關聯關係,系統的擴充性將受到一定的影響,增加新的具體目標類有時候需要修改原有觀察者的代碼,在一定程度上違反了“開閉原則”,但是如果原有觀察者類無須關聯新增的具體目標,則系統擴充性不受影響。
執行個體應用
軟體公司欲開發一款多人聯機對戰遊戲(類似魔獸世界、星際爭霸等遊戲),在該遊戲中,多個玩家可以加入同一戰隊組成聯盟,當戰隊中某一成員受到敵人攻擊時將給所有其他盟友發送通知,盟友收到通知後將作出響應。
在圖22-4中,AllyControlCenter充當目標類,ConcreteAllyControlCenter充當具體目標類,Observer充當抽象觀察者,Player充當具體觀察者。完整代碼如下所示:
import java.util.*;//抽象觀察類interface Observer { public String getName(); public void setName(String name); public void help(); //聲明支援盟友方法 public void beAttacked(AllyControlCenter acc); //聲明遭受攻擊方法}//戰隊成員類:具體觀察者類class Player implements Observer { private String name; public Player(String name) { this.name = name; } public void setName(String name) { this.name = name; } public String getName() { return this.name; } //支援盟友方法的實現 public void help() { System.out.println("堅持住," + this.name + "來救你!"); } //遭受攻擊方法的實現,當遭受攻擊時將調用戰隊控制中心類的通知方法notifyObserver()來通知盟友 public void beAttacked(AllyControlCenter acc) { System.out.println(this.name + "被攻擊!"); acc.notifyObserver(name); }}//戰隊控制中心類:目標類abstract class AllyControlCenter { protected String allyName; //戰隊名稱 protected ArrayList<Observer> players = new ArrayList<Observer>(); //定義一個集合用於儲存戰隊成員 public void setAllyName(String allyName) { this.allyName = allyName; } public String getAllyName() { return this.allyName; } //註冊方法 public void join(Observer obs) { System.out.println(obs.getName() + "加入" + this.allyName + "戰隊!"); players.add(obs); } //登出方法 public void quit(Observer obs) { System.out.println(obs.getName() + "退出" + this.allyName + "戰隊!"); players.remove(obs); } //聲明抽象通知方法 public abstract void notifyObserver(String name);}//具體戰隊控制中心類:具體目標類class ConcreteAllyControlCenter extends AllyControlCenter { public ConcreteAllyControlCenter(String allyName) { System.out.println(allyName + "戰隊組建成功!"); System.out.println("----------------------------"); this.allyName = allyName; } //實現通知方法 public void notifyObserver(String name) { System.out.println(this.allyName + "戰隊緊急通知,盟友" + name + "遭受敵人攻擊!"); //遍曆觀察者集合,調用每一個盟友(自己除外)的支援方法 for(Object obs : players) { if (!((Observer)obs).getName().equalsIgnoreCase(name)) { ((Observer)obs).help(); } } }}
編寫如下用戶端測試代碼:
class Client { public static void main(String args[]) { //定義觀察目標對象 AllyControlCenter acc; acc = new ConcreteAllyControlCenter("金庸群俠"); //定義四個觀察者對象 Observer player1,player2,player3,player4; player1 = new Player("楊過"); acc.join(player1); player2 = new Player("令狐沖"); acc.join(player2); player3 = new Player("張無忌"); acc.join(player3); player4 = new Player("段譽"); acc.join(player4); //某成員遭受攻擊 Player1.beAttacked(acc); } }
編譯並運行程式,輸出結果如下:
金庸群俠戰隊組建成功!----------------------------楊過加入金庸群俠戰隊!令狐沖加入金庸群俠戰隊!張無忌加入金庸群俠戰隊!段譽加入金庸群俠戰隊!楊過被攻擊!金庸群俠戰隊緊急通知,盟友楊過遭受敵人攻擊!堅持住,令狐衝來救你!堅持住,張無忌來救你!堅持住,段譽來救你!
在本執行個體中,實現了兩次對象之間的聯動,當一個遊戲玩家Player對象的beAttacked()方法被調用時,將調用AllyControlCenter的notifyObserver()方法來進行處理,而在notifyObserver()方法中又將調用其他Player對象的help()方法。Player的beAttacked()方法、AllyControlCenter的notifyObserver()方法以及Player的help()方法構成了一個聯動觸發鏈,執行順序如下所示:
Player.beAttacked() –> AllyControlCenter.notifyObserver() –>Player.help()。
JDK對觀察者模式的支援
觀察者模式在Java語言中的地位非常重要。在JDK的java.util包中,提供了Observable類以及Observer介面,它們構成了JDK對觀察者模式的支援。22-5所示:
(1) Observer介面
在java.util.Observer介面中只聲明一個方法,它充當抽象觀察者,其方法聲明代碼如下所示:
void update(Observable o, Object arg);
當觀察目標的狀態發生變化時,該方法將會被調用,在Observer的子類中將實現update()方法,即具體觀察者可以根據需要具有不同的更新行為。當調用觀察目標類Observable的notifyObservers()方法時,將執行觀察者類中的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)
通知方法,用於在方法內部迴圈調用向量中每一個觀察者的update()方法。
deleteObservers()
用於清空向量,即刪除向量中所有觀察者對象。
setChanged()
該方法被調用後會設定一個boolean類型的內部標記變數changed的值為true,表示觀察目標對象的狀態發生了變化。
clearChanged()
用於將changed變數的值設為false,表示對象狀態不再發生改變或者已經通知了所有的觀察者對象,調用了它們的update()方法。
hasChanged()
用於測試對象狀態是否改變。
countObservers()
用於返迴向量中觀察者的數量。
我們可以直接使用Observer介面和Observable類來作為觀察者模式的抽象層,再自訂具體觀察者類和具體觀察目標類,通過使用JDK中的Observer介面和Observable類,可以更加方便地在Java語言中應用觀察者模式。
觀察者模式與Java事件處理
JDK 1.0及更早版本的事件模型基於職責鏈模式,但是這種模型不適用於複雜的系統,因此在JDK 1.1及以後的各個版本中,事件處理模型採用基於觀察者模式的委派事件模型(DelegationEvent Model, DEM),即一個Java組件所引發的事件並不由引發事件的對象自己來負責處理,而是委派給獨立的事件處理對象負責。
在DEM模型中,目標角色(如介面組件)負責發布事件,而觀察者角色(事件處理者)可以向目標訂閱它所感興趣的事件。當一個具體目標產生一個事件時,它將通知所有訂閱者。事件的發行者稱為事件來源(Event Source),而訂閱者稱為事件監聽器(Event Listener),在這個過程中還可以通過事件對象(Event Object)來傳遞與事件相關的資訊,可以在事件監聽者的實作類別中實現事件處理,因此事件監聽對象又可以稱為事件處理對象。事件來源對象、事件監聽對象(事件處理對象)和事件對象構成了Java事件處理模型的三要素。事件來源對象充當觀察目標,而事件監聽對象充當觀察者。以按鈕點擊事件為例,其事件處理流程如下:
(1) 如果使用者在GUI中單擊一個按鈕,將觸發一個事件(如ActionEvent類型的動作事件),JVM將產生一個相應的ActionEvent類型的事件對象,在該事件對象中包含了有關事件和事件來源的資訊,此時按鈕是事件來源對象;
(2) 將ActionEvent事件對象傳遞給事件監聽對象(事件處理對象),JDK提供了專門用於處理ActionEvent事件的介面ActionListener,開發人員需提供一個ActionListener的實作類別(如MyActionHandler),實現在ActionListener介面中聲明的抽象事件處理方法actionPerformed(),對所發生事件做出相應的處理;
(3) 開發人員將ActionListener介面的實作類別(如MyActionHandler)對象註冊到按鈕中,可以通過按鈕類的addActionListener()方法來實現註冊;
(4) JVM在觸發事件時將調用按鈕的fireXXX()方法,在該方法內部將調用註冊到按鈕中的事件處理對象的actionPerformed()方法,實現對事件的處理。
使用類似的方法,我們可自訂GUI組件,如包含兩個文字框和兩個按鈕的登入組件LoginBean,可以採用22-6所示設計方案:
圖22-6中相關類說明如下:
(1) LoginEvent是事件類別,它用於封裝與事件有關的資訊,它不是觀察者模式的一部分,但是它可以在目標對象和觀察者對象之間傳遞資料,在AWT事件模型中,所有的自訂事件類別都是java.util.EventObject的子類。
(2) LoginEventListener充當抽象觀察者,它聲明了事件回應程式法validateLogin(),用於處理事件,該方法也稱為事件處理方法,validateLogin()方法將一個LoginEvent類型的事件對象作為參數,用於傳輸與事件相關的資料,在其子類中實現該方法,實現具體的事件處理。
(3) LoginBean充當具體目標類,在這裡我們沒有定義抽象目標類,對觀察者模式進行了一定的簡化。在LoginBean中定義了抽象觀察者LoginEventListener類型的對象lel和事件對象LoginEvent,提供了註冊方法addLoginEventListener()用於添加觀察者,在Java事件處理中,通常使用的是一對一的觀察者模式,而不是一對多的觀察者模式,也就是說,一個觀察目標中只定義一個觀察者對象,而不是提供一個觀察者對象的集合。在LoginBean中還定義了通知方法fireLoginEvent(),該方法在Java事件處理模型中稱為“點火方法”,在該方法內部執行個體化了一個事件對象LoginEvent,將使用者輸入的資訊傳給觀察者對象,並且調用了觀察者對象的回應程式法validateLogin()。
(4) LoginValidatorA和LoginValidatorB充當具體觀察者類,它們實現了在LoginEventListener介面中聲明的抽象方法validateLogin(),用於具體實現事件處理,該方法包含一個LoginEvent類型的參數,在LoginValidatorA和LoginValidatorB類中可以針對相同的事件提供不同的實現。
觀察者模式與MVC
在當前流行的MVC(Model-View-Controller)架構中也應用了觀察者模式,MVC是一種架構模式,它包含三個角色:模型(Model),視圖(View)和控制器(Controller)。其中模型可對應於觀察者模式中的觀察目標,而視圖對應於觀察者,控制器可充當兩者之間的中介者。當模型層的資料發生改變時,視圖層將自動改變其顯示內容。22-7所示:
在圖22-7中,模型層提供的資料是視圖層所觀察的對象,在視圖層中包含兩個用於顯示資料的圖表對象,一個是柱狀圖,一個是餅狀圖,相同的資料擁有不同的圖表顯示方式,如果模型層的資料發生改變,兩個圖表對象將隨之發生變化,這意味著圖表對象依賴模型層提供的資料對象,因此資料對象的任何狀態改變都應立即通知它們。同時,這兩個圖表之間相互獨立,不存在任何聯絡,而且圖表對象的個數沒有任何限制,使用者可以根據需要再增加新的圖表對象,如折線圖。在增加新的圖表對象時,無須修改原有類庫,滿足“開閉原則”。
總結
觀察者模式是一種使用頻率非常高的設計模式,無論是行動裝置 App、Web應用或者案頭應用,觀察者模式幾乎無處不在,它為實現對象之間的聯動提供了一套完整的解決方案,凡是涉及到一對一或者一對多的對象互動情境都可以使用觀察者模式。觀察者模式廣泛應用於各種程式設計語言的GUI事件處理的實現,在基於事件的XML解析技術(如SAX2)以及Web事件處理中也都使用了觀察者模式。
觀察者模式的優點
觀察者模式的主要優點如下:
(1) 觀察者模式可以實現展示層和資料邏輯層的分離,定義了穩定的訊息更新傳遞機制,並抽象了更新介面,使得可以有各種各樣不同的展示層充當具體觀察者角色。
(2) 觀察者模式在觀察目標和觀察者之間建立一個抽象的耦合。觀察目標只需要維持一個抽象觀察者的集合,無須瞭解其具體觀察者。由於觀察目標和觀察者沒有緊密地耦合在一起,因此它們可以屬於不同的抽象化層次。
(3) 觀察者模式支援廣播通訊,觀察目標會向所有登入的觀察者對象發送通知,簡化了一對多系統設計的難度。
(4) 觀察者模式滿足“開閉原則”的要求,增加新的具體觀察者無須修改原有系統代碼,在具體觀察者與觀察目標之間不存在關聯關係的情況下,增加新的觀察目標也很方便。
觀察者模式的缺點
觀察者模式的主要缺點如下:
(1) 如果一個觀察目標對象有很多直接和間接觀察者,將所有的觀察者都通知到會花費很多時間。
(2) 如果在觀察者和觀察目標之間存在循環相依性,觀察目標會觸發它們之間進行迴圈調用,可能導致系統崩潰。
(3) 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。
觀察者模式適用情境
在以下情況下可以考慮使用觀察者模式:
(1) 一個抽象模型有兩個方面,其中一個方面依賴於另一個方面,將這兩個方面封裝在獨立的對象中使它們可以各自獨立地改變和複用。
(2) 一個對象的改變將導致一個或多個其他對象也發生改變,而並不知道具體有多少對象將發生改變,也不知道這些對象是誰。
(3) 需要在系統中建立一個觸發鏈,A對象的行為將影響B對象,B對象的行為將影響C對象……,可以使用觀察者模式建立一種鏈式觸發機制。
設計模式總結(Java)—— 觀察者模式