觀察者模式

什么是觀察者模式##

有人這么說

觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。

這個主題對象在狀態上發生變化時,會通知所有觀察者對象,讓它們能夠自動更新自己。

還有人這么說

觀察者模式是關于多個對象想知道一個對象中數據變化情況的一種成熟模式。觀察者模式中有一個稱作“主題”的對象和若干個稱作“觀察者”的對象,“主題”和“觀察者”之間是一種一對多的依賴關系。

當“主題”的狀態發生變化時,所有“觀察者”都得到通知。

日常生活中,最容易理解的例子就是微信公眾號。我們用微信訂閱的微信公共號就是這里所說的主題,而我們 每一個關注這個微信號的人就是這里的觀察者。公眾號每天有更新,所有訂閱者都會收到。

這里寫圖片描述

觀察者模式類圖

應用場景##

此種模式通常被用來實現事件處理系統。

觀察者模式組成##

從定義可以看到,該模式必須包含兩個角色:觀察者和被觀察對象(主題)。

從代碼實現的角度,我們又可以分為以下四種角色:

  • 抽象主題角色: 把所有對觀察者對象的引用保存在一個集合中,每個抽象主題角色都可以有任意數量的觀察者。抽象主題提供一個接口,可以增加和刪除觀察者角色。一般用一個抽象類和接口來實現。

  • 抽象觀察者角色:為所有具體的觀察者定義一個接口,在得到主題的通知時更新自己。

  • 具體主題角色:在具體主題內部狀態改變時,給所有登記過的觀察者發出通知。具體主題角色通常用一個子類實現。

  • 具體觀察者角色:該角色實現抽象觀察者角色所要求的更新接口,以便使本身的狀態與主題的狀態相協調。通常用一個子類實現。如果需要,具體觀察者角色可以保存一個指向具體主題角色的引用。

實現觀察者模式##

我們就按照上面提到的4中角色,依次實現:

  • 抽象主題角色

主題接口規定了具體主題需要實現的添加,刪除及通知觀察者更新數據的方法

/**
 * 抽象主題,被觀察者
 *
 */
public interface Subject {
    /**
     * 添加觀察者
     * 
     * @param observer
     */
    void addObserver(Observer observer);

    /**
     * 移除指定的觀察者
     * 
     * @param observer
     */
    void removeObserver(Observer observer);

    /**
     * 移除所有的觀察者
     */
    void removeAll();

    /**
     * data 是要通知給觀察者的數據 因為Object是所有類的父類,可以使用多態,當然 你也可以使用 泛型
     * 
     * @param data
     */
    void notifyAllObserver(Object data);

    /**
     * 單獨 通知某一個觀察者
     * 
     * @param observer
     * @param data
     *            data 是要通知給觀察者的數據 因為Object是所有類的父類,可以使用多態,當然 你也可以使用 泛型
     */
    void notify(Observer observer, Object data);

}
  • 抽象觀察者角色

觀察者接口規定了具體觀察者用來更新數據的方法

/**
 * 抽象觀察者接口
 */
public interface Observer {
    /**
     * 
     * @param subject 被觀察者
     * @param data    被觀察者傳遞給觀察者的 數據
     */
    void update(Subject subject,Object data);
}
  • 具體主題角色
public class ConcreteSubject implements Subject {

    //觀察者集合,用于管理所有的觀察者
    List<Observer> mList = new ArrayList<>();

    @Override
    public void addObserver(Observer observer) {
        // TODO Auto-generated method stub
        // 確保相同的觀察者只含有一個
        if (observer == null) {
            throw new NullPointerException("observer == null");
        }

        if (!mList.contains(observer)) {
            mList.add(observer);
        }
    }

    @Override
    public void removeObserver(Observer observer) {
        // TODO Auto-generated method stub
        mList.remove(observer);
    }

    @Override
    public void removeAll() {
        // TODO Auto-generated method stub
        mList.clear();
    }

    @Override
    public void notifyAllObserver(Object data) {
        // TODO Auto-generated method stub
        for (Observer observer : mList) {
            observer.update(this, data);
        }
    }

    @Override
    public void notify(Observer observer, Object data) {
        // TODO Auto-generated method stub
        if (observer != null) {
            observer.update(this, data);
        }
    }

}
  • 具體的觀察者角色

這里我們可以定義多個具體的觀察者角色

觀察者One

public class ObserverOne implements Observer {

    @Override
    public void update(Subject subject, Object data) {
        // TODO Auto-generated method stub
        System.err
                .println("the messge from subject to-->" + this.getClass().getName() + "<---is " + data.toString());
    }

}

觀察者Two

public class ObserverTwo implements Observer {

    @Override
    public void update(Subject subject, Object data) {
        // TODO Auto-generated method stub
        System.err
        .println("the messge from subject to-->" + this.getClass().getName() + "<---is " + data.toString());
    }

}

觀察者Three

public class ObserverThree implements Observer {

    @Override
    public void update(Subject subject, Object data) {
        // TODO Auto-generated method stub
        System.err
        .println("the messge from subject to-->" + this.getClass().getName() + "<---is " + data.toString());
    }

}

好了,到了這里我們就完成了所有角色的定義。寫個方法測試一下:

  • 測試類
public class TestObservePattern {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        ConcreteSubject concreteSubject = new ConcreteSubject();
        ObserverOne observerOne=new ObserverOne();
        ObserverTwo observerTwo=new ObserverTwo();
        ObserverThree observerThree=new ObserverThree();
        
        concreteSubject.addObserver(observerOne);
        concreteSubject.addObserver(observerTwo);
        concreteSubject.addObserver(observerThree);
        
        
        //通知所有的觀察者
        concreteSubject.notifyAllObserver("wake up,wake up");
        //通知某個特定的觀察者OberverTwo
        concreteSubject.notify(observerTwo, "Specila msg  for you");
        //觀察者ObserveThree 決定不再訂閱主題
        concreteSubject.removeObserver(observerThree);
        //通知所有的觀察者
        concreteSubject.notifyAllObserver("new Message come ");
    }

}

運行程序后輸入日志如下:

通過日志可以看到:

  • 主題內容更新后,所有的觀察者將接收到更新結果。
  • 某個特定的觀察者取消對主題的訂閱后,自身不再接收到主題的更新,而且也不影響主題的實現。

和設置監聽器機制的區別##

初次看到觀察者模式的定義時,感覺這種套路似曾相識。思前想后才發現,觀察者模式其實和平時給Button設置點擊事件的實現方式有些類似。都是作為主題(Button)發生變化(被用戶點擊),觀察者(OnClickListener)接收到通知,并作出響應(onClick回調方法執行)。

看到很多地方將觀察者模式和設置監聽器模式(機制)歸為同一種模式,個人感覺是不太恰當的。

  • 首先,觀察者模式中,抽象觀察者角色以接口的形式存在,注定了所有的具體觀察者角色實現更新(如此處的update)時,方法是唯一的,只有update。 而設置監聽器機制中,為主題(事件源)設置不同的觀察者(監聽器),主題(事件源)發生變化后,觀察者所需實現的方法也是不唯一的。我們這里以Button的click事件和Longclick事件為例:
不同事件

可以看到,不同的觀察者所需實現的方法是完全不一樣的。

  • 其次,觀察者某事中,被觀察者發生變化時,所有觀察者將被動接受通知 。所有具體的觀察者根本無法區分到底是發生了怎樣的變化。當然,也可以通過在主題發送通知時根據不同的狀態分別通知不同的觀察者。但是,這樣就使得被觀察和觀察者之間有了聯系,這是不好的思路。而監聽器機制,通過設置不同的監聽器(即不同的觀察者),便解決了這個問題。

所以,監聽器機制相較于嚴格的觀察者模式還是有區別的。

觀察者模式優缺點##

以下內容摘抄自網絡

觀察者模式有以下的優點:

第一、觀察者模式在被觀察者和觀察者之間建立一個抽象的耦合。被觀察者角色所知道的只是一個具體觀察者列表,每一個具體觀察者都符合一個抽象觀察者的接口。被觀察者并不認識任何一個具體觀察者,它只知道它們都有一個共同的接口。由于被觀察者和觀察者沒有緊密地耦合在一起,因此它們可以屬于不同的抽象化層次。如果被觀察者和觀察者都被扔到一起,那么這個對象必然跨越抽象化和具體化層次。

第二、觀察者模式支持廣播通訊。被觀察者會向所有的登記過的觀察者發出通知,

觀察者模式有下面的缺點:

第一、如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。

第二、如果在被觀察者之間有循環依賴的話,被觀察者會觸發它們之間進行循環調用,導致系統崩潰。在使用觀察者模式是要特別注意這一點。

第三、如果對觀察者的通知是通過另外的線程進行異步投遞的話,系統必須保證投遞是以自恰的方式進行的。

第四、雖然觀察者模式可以隨時使觀察者知道所觀察的對象發生了變化,但是觀察者模式沒有相應的機制使觀察者知道所觀察的對象是怎么發生變化的。


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

推薦閱讀更多精彩內容