Java設計模式之-觀察者模式(Observer)

Java設計模式之-觀察者模式(Observer)

如果你發現一件心儀的商品,但是由于雙十一已經剁手了,只能等到打折再去買。為了及時獲得該商品的打折信息,你有兩種方式:

  • “拉”:每過一會兒就到店里看一下,有沒有相關的打折信息,把信息從店家“拉”到自己眼前;
  • “推”:店家通過短信、郵件等方式將打折信息直接推送到自己眼前,當然你需要先告訴商家我要訂閱這些信息(垃圾短信另當別論

上面兩種方式相比,我當然希望“推”模式,不用自己跑腿,只要和對方建立起信息變更的訂閱關系,就可以被動的收到通知,這就是我們今天要說的觀察者模式。(P.S. 傳統意義上,觀察者可以做成“推”或“拉”,但實際都是“推”,只是推的信息的多少)

觀察者模式

PropertyChangeSupport.PNG
觀察者模式

觀察者模式有5個模塊構成:

  1. 抽象被觀察者:定義了被觀察者的抽象方法,比如添加觀察者,移除觀察者,通知觀察者等;
  2. 具體被觀察者:實現了抽象被觀察者的方法,不一定是extends或implements,也可能是包含了一個專門用于管理觀察者的實例(如PropertyChangeSupport);
  3. 抽象觀察者:定義了觀察者需要實現的方法,該方法是與被觀察者約定好的,統一接口的,表示在接收到被觀察者推來的信息后應該怎樣反饋;
  4. 具體觀察者:實現了抽象觀察者的方法,定義了具體針對通知的響應行為,一般來說會影響客戶端的表現;
  5. 客戶端:直接操作的是抽象被觀察者接口,使被觀察者改變,并獲得反饋。

整個觀察者模式如上所說,現在我們來看一下具體如何實現。

抽象被觀察者

在Java中,抽象被觀察者可以用一個普通類代替,其中保存一個數組或鏈表用于存放需要訂閱變更的抽象觀察者。這種模式比較原始,既然用了Java,最好是不要再重造輪子了,所以我們直接來看一下Java中的一個輪子-PropertyChangeSupport

很多文章中其實提到的是Observable,我學習的時候也是想用這個,但是@Deprecated(since="9") :

 * @deprecated
 * This class and the {@link Observer} interface have been deprecated.
 * The event model supported by {@code Observer} and {@code Observable}
 * is quite limited, the order of notifications delivered by
 * {@code Observable} is unspecified, and state changes are not in
 * one-for-one correspondence with notifications.
 * For a richer event model, consider using the
 * {@link java.beans} package.  For reliable and ordered
 * messaging among threads, consider using one of the concurrent data
 * structures in the {@link java.util.concurrent} package.
 * For reactive streams style programming, see the
 * {@link java.util.concurrent.Flow} API.
 */
@Deprecated(since="9")
public class Observable {

大意就是因為Observable局限性較大,通知的發送順序不可預知,且狀態的改變也不是與通知一一對應的。而它提到的一種替代則是java.beans.PropertyChangeSupport:

PropertyChangeSupport

其中主要的方法是

  • 構造函數
  • addPropertyChangeListener,增加觀察者
  • firePropertyChange,通知觀察者
  • removePropertyChangeListener,移除觀察者

具體被觀察者

一個PropertyChangeSupport已經能夠作為一個抽象被觀察者使用了,現在我們再寫一個具體被觀察者MyEntity:

public class MyEntity {
    private int visitCount;
    private PropertyChangeSupport observer = new PropertyChangeSupport(this);

    public void addObservers(PropertyChangeListener ob) {
        observer.addPropertyChangeListener(ob);
    }

    public void addObservers(String propertyName, PropertyChangeListener ob) {
        observer.addPropertyChangeListener(propertyName, ob);
    }

    public void visit() {
        ++visitCount;
        observer.firePropertyChange("visitCount", visitCount - 1, visitCount);
    }
}

MyEntity有一個visitCount屬性,當visit()被調用時,該屬性值加1,而后調用firePropertyChange通知所有已經訂閱的PropertyChangeListener。可以看到我寫了兩個addObservers,一個有propertyName參數,另一個沒有。

第一個addObservers(PropertyChangeListener ob)會通知所有的ob對象,而第二個addObservers(String propertyName, PropertyChangeListener ob)則是在firePropertyChange()的第一個參數與propertyName一致時才被通知到。

抽象觀察者

其實在上面addObservers中的PropertyChangeListener就是一個抽象觀察者,它其實是一個定義了變更響應方法propertyChange的接口:

/**
 * A "PropertyChange" event gets fired whenever a bean changes a "bound"
 * property.  You can register a PropertyChangeListener with a source
 * bean so as to be notified of any bound property updates.
 * @since 1.1
 */

public interface PropertyChangeListener extends java.util.EventListener {

    /**
     * This method gets called when a bound property is changed.
     * @param evt A PropertyChangeEvent object describing the event source
     *          and the property that has changed.
     */

    void propertyChange(PropertyChangeEvent evt);

}

而如果我們一直翻看firePropertyChange方法的底層代碼,最后可以發現這么一個函數,它循環調用所有listener的propertyChange方法:

    private static void fire(PropertyChangeListener[] listeners, PropertyChangeEvent event) {
        if (listeners != null) {
            for (PropertyChangeListener listener : listeners) {
                listener.propertyChange(event);
            }
        }
    }

具體觀察者

具體觀察者通過實現PropertyChangeListener接口,來定義propertyChange具體應該做什么。我們可以單獨建一個類來做,但更常見的做法是使用匿名內部類:

        MyEntity entity = new MyEntity();
        entity.addObservers(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                System.out.println("Here we comes a change event!");
                System.out.println(evt.toString());
            }
        });

測試使用

其實就是一個簡單的main函數,首先添加兩個Observer,分別使用和不使用帶有propertyName參數的函數,最后調用visit()使變更發生:

public class ObserverTest {
    public static void main(String[] args) {
        MyEntity entity = new MyEntity();
        entity.addObservers(new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                System.out.println("Here we comes a change event!");
                System.out.println(evt.toString());
            }
        });
        entity.addObservers("visitCount", new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                System.out.println("Change Event with property Name");
            }
        });
        entity.visit();
    }
}

該main函數執行結果如下:

Here we comes a change event!
java.beans.PropertyChangeEvent[propertyName=visitCount; oldValue=0; newValue=1; propagationId=null; source=com.mock.observers.MyEntity@77f03bb1]
Change Event with property Name
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,646評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,595評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,560評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,035評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,814評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,224評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,301評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,444評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,988評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,804評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,998評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,544評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,237評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,665評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,927評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,706評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,993評論 2 374

推薦閱讀更多精彩內容