設(shè)計(jì)模式詳解——觀察者模式

本篇文章介紹一種設(shè)計(jì)模式——觀察者模式。觀察者模式在Android中有著很多的應(yīng)用,如按鈕監(jiān)聽,接口回調(diào)以及很多流行框架(RxJava,EventBus)中使用。本篇文章內(nèi)容參考《JAVA與模式》之觀察者模式。

一、觀察者模式的概念

觀察者模式是對(duì)象的行為模式,又叫發(fā)布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監(jiān)聽器(Source/Listener)模式或從屬者(Dependents)模式。

觀察者模式定義了一種一對(duì)多的依賴關(guān)系,讓多個(gè)觀察者對(duì)象同時(shí)監(jiān)聽某一個(gè)主題對(duì)象。這個(gè)主題對(duì)象在狀態(tài)上發(fā)生變化時(shí),會(huì)通知所有觀察者對(duì)象,使它們能夠自動(dòng)更新自己。

二、觀察者模式的結(jié)構(gòu)

一個(gè)軟件系統(tǒng)里面包含了各種對(duì)象,就像一片欣欣向榮的森林充滿了各種生物一樣。在一片森林中,各種生物彼此依賴和約束,形成一個(gè)個(gè)生物鏈。一種生物的狀態(tài)變化會(huì)造成其他一些生物的相應(yīng)行動(dòng),每一個(gè)生物都處于別的生物的互動(dòng)之中。

同樣,一個(gè)軟件系統(tǒng)常常要求在某一個(gè)對(duì)象的狀態(tài)發(fā)生變化的時(shí)候,某些其他的對(duì)象做出相應(yīng)的改變。做到這一點(diǎn)的設(shè)計(jì)方案有很多,但是為了使系統(tǒng)能夠易于復(fù)用,應(yīng)該選擇低耦合度的設(shè)計(jì)方案。減少對(duì)象之間的耦合有利于系統(tǒng)的復(fù)用,但是同時(shí)設(shè)計(jì)師需要使這些低耦合度的對(duì)象之間能夠維持行動(dòng)的協(xié)調(diào)一致,保證高度的協(xié)作。觀察者模式是滿足這一要求的各種設(shè)計(jì)方案中最重要的一種。

下面以一個(gè)簡(jiǎn)單的示意性實(shí)現(xiàn)為例,討論觀察者模式的結(jié)構(gòu)。



觀察者模式所涉及的角色有:

●  抽象主題(Subject)角色:抽象主題角色把所有對(duì)觀察者對(duì)象的引用保存在一個(gè)聚集(比如ArrayList對(duì)象)里,每個(gè)主題都可以有任何數(shù)量的觀察者。抽象主題提供一個(gè)接口,可以增加和刪除觀察者對(duì)象,抽象主題角色又叫做抽象被觀察者(Observable)角色。

●  具體主題(ConcreteSubject)角色:將有關(guān)狀態(tài)存入具體觀察者對(duì)象;在具體主題的內(nèi)部狀態(tài)改變時(shí),給所有登記過的觀察者發(fā)出通知。具體主題角色又叫做具體被觀察者(Concrete Observable)角色。

●  抽象觀察者(Observer)角色:為所有的具體觀察者定義一個(gè)接口,在得到主題的通知時(shí)更新自己,這個(gè)接口叫做更新接口。

●  具體觀察者(ConcreteObserver)角色:存儲(chǔ)與主題的狀態(tài)自恰的狀態(tài)。具體觀察者角色實(shí)現(xiàn)抽象觀察者角色所要求的更新接口,以便使本身的狀態(tài)與主題的狀態(tài)相協(xié)調(diào)。如果需要,具體觀察者角色可以保持一個(gè)指向具體主題對(duì)象的引用。

三、觀察者模式的代碼實(shí)現(xiàn)

抽象主題角色類(此處是抽象類,改成接口將會(huì)提高復(fù)用)

public abstract class Subject {
    /**
     * 用來保存注冊(cè)的觀察者對(duì)象
     */
    private    List<Observer> list = new ArrayList<Observer>();
    /**
     * 注冊(cè)觀察者對(duì)象
     * @param observer    觀察者對(duì)象
     */
    public void attach(Observer observer){
        
        list.add(observer);
        System.out.println("Attached an observer");
    }
    /**
     * 刪除觀察者對(duì)象
     * @param observer    觀察者對(duì)象
     */
    public void detach(Observer observer){
        
        list.remove(observer);
    }
    /**
     * 通知所有注冊(cè)的觀察者對(duì)象
     */
    public void nodifyObservers(String newState){
        
        for(Observer observer : list){
            observer.update(newState);
        }
    }
}

具體主題角色類

public class ConcreteSubject extends Subject{
    
    private String state;
    
    public String getState() {
        return state;
    }

    public void change(String newState){
        state = newState;
        System.out.println("主題狀態(tài)為:" + state);
        //狀態(tài)發(fā)生改變,通知各個(gè)觀察者
        this.nodifyObservers(state);
    }
}

抽象觀察者角色類

public interface Observer {
    /**
     * 更新接口
     * @param state    更新的狀態(tài)
     */
    public void update(String state);
}

具體觀察者角色類

public class ConcreteObserver implements Observer {
    //觀察者的狀態(tài)
    private String observerState;
    
    @Override
    public void update(String state) {
        /**
         * 更新觀察者的狀態(tài),使其與目標(biāo)的狀態(tài)保持一致
         */
        observerState = state;
        System.out.println("狀態(tài)為:"+observerState);
    }
}

具體使用:

public class Client {

    public static void main(String[] args) {
        //創(chuàng)建主題對(duì)象
        ConcreteSubject subject = new ConcreteSubject();
        //創(chuàng)建觀察者對(duì)象
        Observer observer = new ConcreteObserver();
        //將觀察者對(duì)象登記到主題對(duì)象上
        subject.attach(observer);
        //改變主題對(duì)象的狀態(tài)
        subject.change("new state");
    }
}

當(dāng)主題對(duì)象的狀態(tài)改變時(shí),將通知所有觀察者,觀察者接收到主題對(duì)象的通知后,將可以進(jìn)行其他操作,進(jìn)行響應(yīng)。

四、推模型和拉模型

在觀察者模式中,又分為推模型和拉模型兩種方式。

●  推模型

主題對(duì)象向觀察者推送主題的詳細(xì)信息,不管觀察者是否需要,推送的信息通常是主題對(duì)象的全部或部分?jǐn)?shù)據(jù)。

●  拉模型

主題對(duì)象在通知觀察者的時(shí)候,只傳遞少量信息。如果觀察者需要更具體的信息,由觀察者主動(dòng)到主題對(duì)象中獲取,相當(dāng)于是觀察者從主題對(duì)象中拉數(shù)據(jù)。一般這種模型的實(shí)現(xiàn)中,會(huì)把主題對(duì)象自身通過update()方法傳遞給觀察者,這樣在觀察者需要獲取數(shù)據(jù)的時(shí)候,就可以通過這個(gè)引用來獲取了。

根據(jù)上面的描述,發(fā)現(xiàn)前面的例子就是典型的推模型,下面給出一個(gè)拉模型的實(shí)例。

拉模型的抽象觀察者類

拉模型通常都是把主題對(duì)象當(dāng)做參數(shù)傳遞。

public interface Observer {
    /**
     * 更新接口
     * @param subject 傳入主題對(duì)象,方面獲取相應(yīng)的主題對(duì)象的狀態(tài)
     */
    public void update(Subject subject);
}

拉模型的具體觀察者類

public class ConcreteObserver implements Observer {
    //觀察者的狀態(tài)
    private String observerState;
    
    @Override
    public void update(Subject subject) {
        /**
         * 更新觀察者的狀態(tài),使其與目標(biāo)的狀態(tài)保持一致
         */
        observerState = ((ConcreteSubject)subject).getState();
        System.out.println("觀察者狀態(tài)為:"+observerState);
    }

}

拉模型的抽象主題類
拉模型的抽象主題類主要的改變是nodifyObservers()方法。在循環(huán)通知觀察者的時(shí)候,也就是循環(huán)調(diào)用觀察者的update()方法的時(shí)候,傳入的參數(shù)不同了。

public abstract class Subject {
    /**
     * 用來保存注冊(cè)的觀察者對(duì)象
     */
    private    List<Observer> list = new ArrayList<Observer>();
    /**
     * 注冊(cè)觀察者對(duì)象
     * @param observer    觀察者對(duì)象
     */
    public void attach(Observer observer){
        
        list.add(observer);
        System.out.println("Attached an observer");
    }
    /**
     * 刪除觀察者對(duì)象
     * @param observer    觀察者對(duì)象
     */
    public void detach(Observer observer){
        
        list.remove(observer);
    }
    /**
     * 通知所有注冊(cè)的觀察者對(duì)象
     */
    public void nodifyObservers(){
        
        for(Observer observer : list){
            observer.update(this);
        }
    }
}

拉模型的具體主題類
跟推模型相比,有一點(diǎn)變化,就是調(diào)用通知觀察者的方法的時(shí)候,不需要傳入?yún)?shù)了。

public class ConcreteSubject extends Subject{
    
    private String state;
    
    public String getState() {
        return state;
    }

    public void change(String newState){
        state = newState;
        System.out.println("主題狀態(tài)為:" + state);
        //狀態(tài)發(fā)生改變,通知各個(gè)觀察者
        this.nodifyObservers();
    }
}
兩種模式的比較

■  推模型是假定主題對(duì)象知道觀察者需要的數(shù)據(jù);而拉模型是主題對(duì)象不知道觀察者具體需要什么數(shù)據(jù),沒有辦法的情況下,干脆把自身傳遞給觀察者,讓觀察者自己去按需要取值。

■  推模型可能會(huì)使得觀察者對(duì)象難以復(fù)用,因?yàn)橛^察者的update()方法是按需要定義的參數(shù),可能無法兼顧沒有考慮到的使用情況。這就意味著出現(xiàn)新情況的時(shí)候,就可能提供新的update()方法,或者是干脆重新實(shí)現(xiàn)觀察者;而拉模型就不會(huì)造成這樣的情況,因?yàn)槔P拖拢瑄pdate()方法的參數(shù)是主題對(duì)象本身,這基本上是主題對(duì)象能傳遞的最大數(shù)據(jù)集合了,基本上可以適應(yīng)各種情況的需要。

五、JAVA提供的對(duì)觀察者模式的支持

在JAVA語言的java.util庫里面,提供了一個(gè)Observable類以及一個(gè)Observer接口,構(gòu)成JAVA語言對(duì)觀察者模式的支持。

Observer接口
這個(gè)接口只定義了一個(gè)方法,即update()方法,當(dāng)被觀察者對(duì)象的狀態(tài)發(fā)生變化時(shí),被觀察者對(duì)象的notifyObservers()方法就會(huì)調(diào)用這一方法。

public interface Observer {

    void update(Observable o, Object arg);
}

Observable類
被觀察者類都是java.util.Observable類的子類。java.util.Observable提供公開的方法支持觀察者對(duì)象,這些方法中有兩個(gè)對(duì)Observable的子類非常重要:一個(gè)是setChanged(),另一個(gè)是notifyObservers()。第一方法setChanged()被調(diào)用之后會(huì)設(shè)置一個(gè)內(nèi)部標(biāo)記變量,代表被觀察者對(duì)象的狀態(tài)發(fā)生了變化。第二個(gè)是notifyObservers(),這個(gè)方法被調(diào)用時(shí),會(huì)調(diào)用所有登記過的觀察者對(duì)象的update()方法,使這些觀察者對(duì)象可以更新自己。

public class Observable {
    private boolean changed = false;
    private Vector obs;
   
    /** Construct an Observable with zero Observers. */

    public Observable() {
    obs = new Vector();
    }

    /**
     * 將一個(gè)觀察者添加到觀察者聚集上面
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
    if (!obs.contains(o)) {
        obs.addElement(o);
    }
    }

    /**
     * 將一個(gè)觀察者從觀察者聚集上刪除
     */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    public void notifyObservers() {
    notifyObservers(null);
    }

    /**
     * 如果本對(duì)象有變化(那時(shí)hasChanged 方法會(huì)返回true)
     * 調(diào)用本方法通知所有登記的觀察者,即調(diào)用它們的update()方法
     * 傳入this和arg作為參數(shù)
     */
    public void notifyObservers(Object arg) {

        Object[] arrLocal;

    synchronized (this) {

        if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    /**
     * 將觀察者聚集清空
     */
    public synchronized void deleteObservers() {
    obs.removeAllElements();
    }

    /**
     * 將“已變化”設(shè)置為true
     */
    protected synchronized void setChanged() {
    changed = true;
    }

    /**
     * 將“已變化”重置為false
     */
    protected synchronized void clearChanged() {
    changed = false;
    }

    /**
     * 檢測(cè)本對(duì)象是否已變化
     */
    public synchronized boolean hasChanged() {
    return changed;
    }

    /**
     * Returns the number of observers of this <tt>Observable</tt> object.
     *
     * @return  the number of observers of this object.
     */
    public synchronized int countObservers() {
    return obs.size();
    }
}

這個(gè)類代表一個(gè)被觀察者對(duì)象,有時(shí)稱之為主題對(duì)象。一個(gè)被觀察者對(duì)象可以有數(shù)個(gè)觀察者對(duì)象,每個(gè)觀察者對(duì)象都是實(shí)現(xiàn)Observer接口的對(duì)象。在被觀察者發(fā)生變化時(shí),會(huì)調(diào)用Observable的notifyObservers()方法,此方法調(diào)用所有的具體觀察者的update()方法,從而使所有的觀察者都被通知更新自己。

使用JAVA對(duì)觀察者模式的支持

這里給出一個(gè)非常簡(jiǎn)單的例子,說明怎樣使用JAVA所提供的對(duì)觀察者模式的支持。在這個(gè)例子中,被觀察對(duì)象叫做Watched;而觀察者對(duì)象叫做Watcher。Watched對(duì)象繼承自java.util.Observable類;而Watcher對(duì)象實(shí)現(xiàn)了java.util.Observer接口。另外有一個(gè)Test類扮演客戶端角色。
被觀察者Watched類

public class Watched extends Observable{
    
    private String data = "";
    
    public String getData() {
        return data;
    }

    public void setData(String data) {
        
        if(!this.data.equals(data)){
            this.data = data;
            setChanged();
           notifyObservers();
        }  
    }
    
    
}

觀察者Watcher 類

public class Watcher implements Observer{
    
    public Watcher(Observable o){
        o.addObserver(this);
    }
    
    @Override
    public void update(Observable o, Object arg) {
        
        System.out.println("狀態(tài)發(fā)生改變:" + ((Watched)o).getData());
    }
}

具體使用:

public static void main(String[] args) {
        
        //創(chuàng)建被觀察者對(duì)象
        Watched watched = new Watched();
        //創(chuàng)建觀察者對(duì)象,并將被觀察者對(duì)象登記
        Observer watcher = new Watcher(watched);
        //給被觀察者狀態(tài)賦值
        watched.setData("start");
        watched.setData("run");
        watched.setData("stop");

    }
總結(jié):

觀察者模式一般是一對(duì)多的情形,本文中篇幅有限,都是一對(duì)一的例子。當(dāng)一對(duì)多時(shí),通過Java內(nèi)置的觀察者模式時(shí),通知多個(gè)觀察者的順序不是固定的。所以如果依賴此順序的話,要自己實(shí)現(xiàn)觀察者模式。同時(shí)Java內(nèi)置的觀察者模式中,Observerable是個(gè)類,所以在子類繼承了該類后就不能繼承其他類,導(dǎo)致復(fù)用受到限制,自己實(shí)現(xiàn)觀察者模式時(shí)可以設(shè)置為接口,提高復(fù)用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 設(shè)計(jì)模式匯總 一、基礎(chǔ)知識(shí) 1. 設(shè)計(jì)模式概述 定義:設(shè)計(jì)模式(Design Pattern)是一套被反復(fù)使用、多...
    MinoyJet閱讀 4,096評(píng)論 1 15
  • 一、 概述 觀察者模式是對(duì)象的行為模式,又叫發(fā)布-訂閱(Publish/Subscribe)模式、模型-視圖(Mo...
    步積閱讀 1,362評(píng)論 0 1
  • 本文的結(jié)構(gòu)如下: 什么是觀察者模式 為什么要用該模式 模式的結(jié)構(gòu) 代碼示例 推模型和拉模型 優(yōu)點(diǎn)和缺點(diǎn) 適用環(huán)境 ...
    w1992wishes閱讀 1,524評(píng)論 0 16
  • 1 場(chǎng)景問題# 1.1 訂閱報(bào)紙的過程## 來考慮實(shí)際生活中訂閱報(bào)紙的過程,這里簡(jiǎn)單總結(jié)了一下,訂閱報(bào)紙的基本流程...
    七寸知架構(gòu)閱讀 4,814評(píng)論 5 57
  • 之前使用的是svn,svn每次提交的版本號(hào)是一個(gè)數(shù)字,用來作為內(nèi)部版本號(hào),確定打出的包所對(duì)應(yīng)的代碼庫版本。 后來換...
    堯月閱讀 7,789評(píng)論 1 4

友情鏈接更多精彩內(nèi)容