設計模式總結(Java)—— 觀察者模式

概述

它用於創建一種對象與對象之間的依賴關係,一個對象發生改變時將自動通知其餘對象,其餘對象將相應做出反應。在觀察者模式中,發生改變的對象稱爲觀察目標,而被通知的對象稱爲觀察者,一個觀察目標能夠對應多個觀察者,並且這些觀察者之間能夠沒有任何相互聯繫,能夠根據須要增長和刪除觀察者,使得系統更易於擴展。java

觀察者模式(Observer Pattern):定義對象之間的一種一對多依賴關係,使得每當一個對象狀態發生改變時,其相關依賴對象皆獲得通知並被自動更新。觀察者模式的別名包括髮布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。觀察者模式是一種對象行爲型模式。編程

 

觀察者模式結構

觀察者模式結構中一般包括觀察目標和觀察者兩個繼承層次結構,其結構如圖所示:設計模式

這裏寫圖片描述

在觀察者模式結構圖中包含以下幾個角色: 
● Subject(目標):目標又稱爲主題,它是指被觀察的對象。在目標中定義了一個觀察者集合,一個觀察目標能夠接受任意數量的觀察者來觀察,它提供一系列方法來增長和刪除觀察者對象,同時它定義了通知方法notify()。目標類能夠是接口,也能夠是抽象類或具體類。 
● ConcreteSubject(具體目標):具體目標是目標類的子類,一般它包含有常常發生改變的數據,當它的狀態發生改變時,向它的各個觀察者發出通知;同時它還實現了在目標類中定義的抽象業務邏輯方法(若是有的話)。若是無須擴展目標類,則具體目標類能夠省略。 
● Observer(觀察者):觀察者將對觀察目標的改變作出反應,觀察者通常定義爲接口,該接口聲明瞭更新數據的方法update(),所以又稱爲抽象觀察者。 
● ConcreteObserver(具體觀察者):在具體觀察者中維護一個指向具體目標對象的引用,它存儲具體觀察者的有關狀態,這些狀態須要和具體目標的狀態保持一致;它實現了在抽象觀察者Observer中定義的update()方法。一般在實現時,能夠調用具體目標類的attach()方法將本身添加到目標類的集合中或經過detach()方法將本身從目標類的集合中刪除。 
觀察者模式描述瞭如何創建對象與對象之間的依賴關係,以及如何構造知足這種需求的系統。觀察者模式包含觀察目標和觀察者兩類對象,一個目標能夠有任意數目的與之相依賴的觀察者,一旦觀察目標的狀態發生改變,全部的觀察者都將獲得通知。做爲對這個通知的響應,每一個觀察者都將監視觀察目標的狀態以使其狀態與目標狀態同步,這種交互也稱爲發佈-訂閱(Publish-Subscribe)。觀察目標是通知的發佈者,它發出通知時並不須要知道誰是它的觀察者,能夠有任意數目的觀察者訂閱它並接收通知。架構

觀察者模式典型代碼

下面經過示意代碼來對該模式進行進一步分析。首先咱們定義一個抽象目標Subject,典型代碼以下所示mvc

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()方法,其典型代碼以下所示:ui

class ConcreteObserver implements Observer {  
    //實現響應方法  
    public void update() {  
        //具體響應代碼  
    }  
} 

 

在有些更加複雜的狀況下,具體觀察者類ConcreteObserver的update()方法在執行時須要使用到具體目標類ConcreteSubject中的狀態(屬性),所以在ConcreteObserver與ConcreteSubject之間有時候還存在關聯或依賴關係,在ConcreteObserver中定義一個ConcreteSubject實例,經過該實例獲取存儲在ConcreteSubject中的狀態。若是ConcreteObserver的update()方法不須要使用到ConcreteSubject中的狀態屬性,則能夠對觀察者模式的標準結構進行簡化,在具體觀察者ConcreteObserver和具體目標ConcreteSubject之間無須維持對象引用。若是在具體層具備關聯關係,系統的擴展性將受到必定的影響,增長新的具體目標類有時候須要修改原有觀察者的代碼,在必定程度上違反了「開閉原則」,可是若是原有觀察者類無須關聯新增的具體目標,則系統擴展性不受影響。this

實例應用

軟件公司欲開發一款多人聯機對戰遊戲(相似魔獸世界、星際爭霸等遊戲),在該遊戲中,多個玩家能夠加入同一戰隊組成聯盟,當戰隊中某一成員受到敵人攻擊時將給全部其餘盟友發送通知,盟友收到通知後將做出響應。spa

這裏寫圖片描述

在圖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中,模型層提供的數據是視圖層所觀察的對象,在視圖層中包含兩個用於顯示數據的圖表對象,一個是柱狀圖,一個是餅狀圖,相同的數據擁有不一樣的圖表顯示方式,若是模型層的數據發生改變,兩個圖表對象將隨之發生變化,這意味着圖表對象依賴模型層提供的數據對象,所以數據對象的任何狀態改變都應當即通知它們。同時,這兩個圖表之間相互獨立,不存在任何聯繫,並且圖表對象的個數沒有任何限制,用戶能夠根據須要再增長新的圖表對象,如折線圖。在增長新的圖表對象時,無須修改原有類庫,知足「開閉原則」。

總結

觀察者模式是一種使用頻率很是高的設計模式,不管是移動應用、Web應用或者桌面應用,觀察者模式幾乎無處不在,它爲實現對象之間的聯動提供了一套完整的解決方案,凡是涉及到一對一或者一對多的對象交互場景均可以使用觀察者模式。觀察者模式普遍應用於各類編程語言的GUI事件處理的實現,在基於事件的XML解析技術(如SAX2)以及Web事件處理中也都使用了觀察者模式。

觀察者模式的優勢

觀察者模式的主要優勢以下: 
(1) 觀察者模式能夠實現表示層和數據邏輯層的分離,定義了穩定的消息更新傳遞機制,並抽象了更新接口,使得能夠有各類各樣不一樣的表示層充當具體觀察者角色。 
(2) 觀察者模式在觀察目標和觀察者之間創建一個抽象的耦合。觀察目標只須要維持一個抽象觀察者的集合,無須瞭解其具體觀察者。因爲觀察目標和觀察者沒有緊密地耦合在一塊兒,所以它們能夠屬於不一樣的抽象化層次。 
(3) 觀察者模式支持廣播通訊,觀察目標會向全部已註冊的觀察者對象發送通知,簡化了一對多系統設計的難度。 
(4) 觀察者模式知足「開閉原則」的要求,增長新的具體觀察者無須修改原有系統代碼,在具體觀察者與觀察目標之間不存在關聯關係的狀況下,增長新的觀察目標也很方便。

觀察者模式的缺點

觀察者模式的主要缺點以下: 
(1) 若是一個觀察目標對象有不少直接和間接觀察者,將全部的觀察者都通知到會花費不少時間。 
(2) 若是在觀察者和觀察目標之間存在循環依賴,觀察目標會觸發它們之間進行循環調用,可能致使系統崩潰。 
(3) 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。

觀察者模式適用場景

在如下狀況下能夠考慮使用觀察者模式: (1) 一個抽象模型有兩個方面,其中一個方面依賴於另外一個方面,將這兩個方面封裝在獨立的對象中使它們能夠各自獨立地改變和複用。 (2) 一個對象的改變將致使一個或多個其餘對象也發生改變,而並不知道具體有多少對象將發生改變,也不知道這些對象是誰。 (3) 須要在系統中建立一個觸發鏈,A對象的行爲將影響B對象,B對象的行爲將影響C對象……,可使用觀察者模式建立一種鏈式觸發機制。

相關文章
相關標籤/搜索