java與設計模式-觀察者模式

java與設計模式-觀察者模式

一、定義

觀察者模式(Observer Pattern) 也叫做發佈訂閱模式(Publish/subscribe) ,它是一個在項
目中經常使用的模式, 其定義如下:Define a one-to-many dependency between objects so that when one object changes state,all itsdependents are notified and updated automatically.(定義對象間一種一對多的依賴關係, 使得每當一個對象改變狀態, 則所有依賴於它的對象都會得到通知並被自動更新。 )

二、通用類圖

在這裏插入圖片描述

三、角色分析

  • Subject被觀察者
    定義被觀察者必須實現的職責, 它必須能夠動態地增加、 取消觀察者。 它一般是抽象類或者是實現類, 僅僅完成作爲被觀察者必須實現的職責: 管理觀察者並通知觀察者。

  • Observer觀察者
    觀察者接收到消息後, 即進行update(更新方法) 操作, 對接收到的信息進行處理。

  • ConcreteSubject具體的被觀察者
    定義被觀察者自己的業務邏輯, 同時定義對哪些事件進行通知.

  • ConcreteObserver具體的觀察者
    每個觀察在接收到消息後的處理反應是不同, 各個觀察者有自己的處理邏輯。

四、經典代碼實現

Observer觀察者

觀察者一般是一個接口, 每一個實現該接口的實現類都是具體觀察者。

public interface Observer {

    /**
     * 更新方法
     */
    void update();
}

Observer具體觀察者1

public class ConcreteObserver01 implements Observer {


    @Override
    public void update() {
        System.out.println("間諜-01:觀察到被觀察者有變化,通知上級...");
    }
}

Observer具體觀察者2

public class ConcreteObserver02 implements Observer {


    @Override
    public void update() {
        System.out.println("間諜-02:觀察到被觀察者有變化,通知上級...");
    }
}

被觀察者

被觀察者的職責非常簡單, 就是定義誰能夠觀察, 誰不能觀察, 程序中使用ArrayList和Vector沒有太大的差別, ArrayList是線程異步, 不安全; Vector是線程同步, 安全——就這點區別。 我們再來看具體的被觀察者。

public abstract class Subject {

    /**
     * 觀察者列表
     */
    private Vector<Observer> observers = new Vector<>();

    /**
     * 添加觀察者
     *
     * @param observer 觀察者
     */
    public void addObserver(Observer observer) {
        this.observers.add(observer);
    }


    /**
     * 刪除觀察者
     *
     * @param observer 觀察者
     */
    public void removeObserver(Observer observer) {
        this.observers.remove(observer);
    }

    /**
     * 通知所有觀察者
     *
     */
    public void notifyObserver() {
        observers.forEach(Observer::update);
    }

}

具體被觀察者

public class ConcreteSubject extends Subject {

    public void doSomething() {
        System.out.println("具體被觀察者: 我進行了一些計算活動...");
        super.notifyObserver();
    }
}

場景類

public class Main {

    public static void main(String[] args) {
        ConcreteSubject cs = new ConcreteSubject();
        ConcreteObserver01 co1 = new ConcreteObserver01();
        ConcreteObserver02 co2 = new ConcreteObserver02();

        cs.addObserver(co1);
        cs.addObserver(co2);
        cs.doSomething();
    }
}

運行結果:

具體被觀察者: 我進行了一些計算活動...
間諜-01:觀察到被觀察者有變化,通知上級...
間諜-02:觀察到被觀察者有變化,通知上級...

五、觀察者模式的應用與理解

5.1 觀察者模式的優點

  • 觀察者和被觀察者之間是抽象耦合
    如此設計, 則不管是增加觀察者還是被觀察者都非常容易擴展, 而且在Java中都已經實現的抽象層級的定義, 在系統擴展方面更是得心應手

  • 建立一套觸發機制
    根據單一職責原則, 每個類的職責是單一的, 那麼怎麼把各個單一的職責串聯成真實世界的複雜的邏輯關係呢? 比如, 我們去打獵, 打死了一隻母鹿, 母鹿有三個幼崽, 因失去了母鹿而餓死, 屍體又被兩隻禿鷹爭搶, 因分配不均, 禿鷹開始鬥毆, 然後羸弱的禿鷹死掉,生存下來的禿鷹, 則因此擴大了地盤……這就是一個觸發機制, 形成了一個觸發鏈。 觀察者模式可以完美地實現這裏的鏈條形式。

5.2 觀察者模式的缺點

觀察者模式需要考慮一下開發效率和運行效率問題, 一個被觀察者, 多個觀察者, 開發和調試就會比較複雜, 而且在Java中消息的通知默認是順序執行, 一個觀察者卡殼, 會影響整體的執行效率。 在這種情況下, 一般考慮採用異步的方式。

多級觸發時的效率更是讓人擔憂, 大家在設計時注意考慮。

5.3 觀察者模式的使用場景

  • 關聯行爲場景。 需要注意的是, 關聯行爲是可拆分的, 而不是“組合”關係。
  • 事件多級觸發場景。
  • 跨系統的消息交換場景, 如消息隊列的處理機制

5.3 觀察者模式的注意事項

使用觀察者模式也有以下兩個重點問題要解決.

  • 廣播鏈的問題

如果你做過數據庫的觸發器, 你就應該知道有一個觸發器鏈的問題, 比如表A上寫了一個觸發器, 內容是一個字段更新後更新表B的一條數據, 而表B上也有個觸發器, 要更新表C, 表C也有觸發器……完蛋了, 這個數據庫基本上就毀掉了! 我們的觀察者模式也是一樣的問題, 一個觀察者可以有雙重身份, 既是觀察者, 也是被觀察者, 這沒什麼問題呀, 但是鏈一旦建立, 這個邏輯就比較複雜, 可維護性非常差, 根據經驗建議, 在一個觀察者模式中最多出現一個對象既是觀察者也是被觀察者, 也就是說消息最多轉發一次(傳遞兩次) , 這還是比較好控制的。

注意 它和責任鏈模式的最大區別就是觀察者廣播鏈在傳播的過程中消息是隨時更改的, 它是由相鄰的兩個節點協商的消息結構; 而責任鏈模式在消息傳遞過程中基本上保持消息不可變, 如果要改變, 也只是在原有的消息上進行修正。

  • 異步處理問題

這個EJB是一個非常好的例子, 被觀察者發生動作了, 觀察者要做出迴應, 如果觀察者比較多, 而且處理時間比較長怎麼辦? 那就用異步唄, 異步處理就要考慮線程安全和隊列的問題, 這個大家有時間看Message Queue, 就會有更深的瞭解。

5.4 觀察者模式的擴展

Java世界中的觀察者模式

Java從一開始誕生就提供了一個可擴展的父類,即java.util.Observable, 這個類就是爲那些“暴露狂”準備的,他們老是喜歡把自己的狀態變更讓別人去欣賞, 去觸發。 我們打開Java的幫助文件看看, 查找一下Observable是不是已經有這個類了? JDK中提供了:java.util.Observable實現類和java.util.Observer接口, 也就是說我們上面寫的那個例子中的Observable接口可以改換成java.util.Observale實現類了。

public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;

    /** Construct an Observable with zero Observers. */

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

    /**
     * Adds an observer to the set of observers for this object, provided
     * that it is not the same as some observer already in the set.
     * The order in which notifications will be delivered to multiple
     * observers is not specified. See the class comment.
     *
     * @param   o   an observer to be added.
     * @throws NullPointerException   if the parameter o is null.
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    /**
     * Deletes an observer from the set of observers of this object.
     * Passing <CODE>null</CODE> to this method will have no effect.
     * @param   o   the observer to be deleted.
     */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    /**
     * If this object has changed, as indicated by the
     * <code>hasChanged</code> method, then notify all of its observers
     * and then call the <code>clearChanged</code> method to
     * indicate that this object has no longer changed.
     * <p>
     * Each observer has its <code>update</code> method called with two
     * arguments: this observable object and <code>null</code>. In other
     * words, this method is equivalent to:
     * <blockquote><tt>
     * notifyObservers(null)</tt></blockquote>
     *
     * @see     java.util.Observable#clearChanged()
     * @see     java.util.Observable#hasChanged()
     * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
     */
    public void notifyObservers() {
        notifyObservers(null);
    }

    /**
     * If this object has changed, as indicated by the
     * <code>hasChanged</code> method, then notify all of its observers
     * and then call the <code>clearChanged</code> method to indicate
     * that this object has no longer changed.
     * <p>
     * Each observer has its <code>update</code> method called with two
     * arguments: this observable object and the <code>arg</code> argument.
     *
     * @param   arg   any object.
     * @see     java.util.Observable#clearChanged()
     * @see     java.util.Observable#hasChanged()
     * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
     */
    public void notifyObservers(Object arg) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        Object[] arrLocal;

        synchronized (this) {
            /* We don't want the Observer doing callbacks into
             * arbitrary code while holding its own Monitor.
             * The code where we extract each Observable from
             * the Vector and store the state of the Observer
             * needs synchronization, but notifying observers
             * does not (should not).  The worst result of any
             * potential race-condition here is that:
             * 1) a newly-added Observer will miss a
             *   notification in progress
             * 2) a recently unregistered Observer will be
             *   wrongly notified when it doesn't care
             */
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

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

    /**
     * Clears the observer list so that this object no longer has any observers.
     */
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    /**
     * Marks this <tt>Observable</tt> object as having been changed; the
     * <tt>hasChanged</tt> method will now return <tt>true</tt>.
     */
    protected synchronized void setChanged() {
        changed = true;
    }

    /**
     * Indicates that this object has no longer changed, or that it has
     * already notified all of its observers of its most recent change,
     * so that the <tt>hasChanged</tt> method will now return <tt>false</tt>.
     * This method is called automatically by the
     * <code>notifyObservers</code> methods.
     *
     * @see     java.util.Observable#notifyObservers()
     * @see     java.util.Observable#notifyObservers(java.lang.Object)
     */
    protected synchronized void clearChanged() {
        changed = false;
    }

    /**
     * Tests if this object has changed.
     *
     * @return  <code>true</code> if and only if the <code>setChanged</code>
     *          method has been called more recently than the
     *          <code>clearChanged</code> method on this object;
     *          <code>false</code> otherwise.
     * @see     java.util.Observable#clearChanged()
     * @see     java.util.Observable#setChanged()
     */
    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();
    }
}
public interface Observer {
    /**
     * This method is called whenever the observed object is changed. An
     * application calls an <tt>Observable</tt> object's
     * <code>notifyObservers</code> method to have all the object's
     * observers notified of the change.
     *
     * @param   o     the observable object.
     * @param   arg   an argument passed to the <code>notifyObservers</code>
     *                 method.
     */
    void update(Observable o, Object arg);
}

5.5 項目中真實的觀察者模式

爲什麼要說“真實”呢? 因爲我們剛剛講的那些是太標準的模式了, 在系統設計中會對觀察者模式進行改造或改裝, 主要在以下3個方面.

  • 觀察者和被觀察者之間的消息溝通

被觀察者狀態改變會觸發觀察者的一個行爲, 同時會傳遞一個消息給觀察者, 這是正確的, 在實際中一般的做法是: 觀察者中的update方法接受兩個參數, 一個是被觀察者, 一個是DTO(Data Transfer Object, 據傳輸對象) , DTO一般是一個純潔的JavaBean,由被觀察者生成, 由觀察者消費。

當然, 如果考慮到遠程傳輸, 一般消息是以XML或JSON格式傳遞。

  • 觀察者響應方式

我們這樣來想一個問題, 觀察者是一個比較複雜的邏輯, 它要接受被觀察者傳遞過來的信息, 同時還要對他們進行邏輯處理, 在一個觀察者多個被觀察者的情況下, 性能就需要提到日程上來考慮了, 爲什麼呢? 如果觀察者來不及響應, 被觀察者的執行時間是不是也會被拉長? 那現在的問題就是: 觀察者如何快速響應? 有兩個辦法: 一是採用多線程技術, 甭管是被觀察者啓動線程還是觀察者啓動線程, 都可以明顯地提高系統性能, 這也就是大家通常所說的異步架構; 二是緩存技術, 甭管你誰來, 我已經準備了足夠的資源給你了, 我保證快速響應, 這當然也是一種比較好方案, 代價就是開發難度很大, 而且壓力測試要做的足夠充
分, 這種方案也就是大家說的同步架構。

  • 被觀察者儘量自己做主

這是什麼意思呢? 被觀察者的狀態改變是否一定要通知觀察者呢? 不一定吧, 在設計的時候要靈活考慮, 否則會加重觀察者的處理邏輯, 一般是這樣做的, 對被觀察者的業務邏輯doSomething方法實現重載, 如增加一個doSomething(boolean isNotifyObs)方法, 決定是否通知觀察者, 而不是在消息到達觀察者時才判斷是否要消費.

5.6 訂閱發佈模型

觀察者模式也叫做發佈/訂閱模型(Publish/Subscribe) , 如果你做過EJB(EnterpriseJavaBean) 的開發, 這個你絕對不會陌生。 EJB2是個折騰死人不償命的玩意兒, 寫個Bean要實現, 還要繼承, 再加上那一堆的配置文件, 小項目還湊合, 你要知道用EJB開發的基本上都不是小項目, 到最後是每個項目成員都在罵EJB這個忽悠人的東西; 但是EJB3是個非常優秀的框架, 還是算比較輕量級, 寫個Bean只要加個Annotaion就成了, 配置文件減少了, 而且也引入了依賴注入的概念, 雖然只是EJB2的翻版, 但是畢竟還是前進了一步。 在EJB中有3個類型的Bean: Session Bean、 Entity Bean和MessageDriven Bean, 我們這裏來說一下MessageDriven Bean(一般簡稱爲MDB) , 消息驅動Bean, 消息的發佈者(Provider) 發佈一個消息, 也就是一個消息驅動Bean, 通過EJB容器(一般是Message Queue消息隊列) 通知訂閱者做出迴應, 從原理上看很簡單, 就是觀察者模式的升級版, 或者說是觀察則模式的BOSS版。

六、最佳實踐

觀察者模式在實際項目和生活中非常常見, 我們舉幾個經常發生的例子來說明.

  • 文件系統

比如, 在一個目錄下新建立一個文件, 這個動作會同時通知目錄管理器增加該目錄, 並通知磁盤管理器減少1KB的空間, 也就說“文件”是一個被觀察者, “目錄管理器”和“磁盤管理器”則是觀察者。

  • 貓鼠遊戲

夜裏貓叫一聲, 家裏的老鼠撒腿就跑, 同時也吵醒了熟睡的主人, 這個場景中, “貓”就是被觀察者, 老鼠和人則是觀察者。

  • ATM取錢

比如你到ATM機器上取錢, 多次輸錯密碼, 卡就會被ATM吞掉, 吞卡動作發生的時候, 會觸發哪些事件呢? 第一, 攝像頭連續快拍, 第二, 通知監控系統, 吞卡發生; 第三,初始化ATM機屏幕, 返回最初狀態。 一般前兩個動作都是通過觀察者模式來完成的, 後一個動作是異常來完成。

  • 廣播收音機

電臺在廣播, 你可以打開一個收音機, 或者兩個收音機來收聽, 電臺就是被觀察者, 收音機就是觀察者。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章