觀察者模式——心有靈犀

一缠俺、定義

定義對象間一種一對多的依賴關(guān)系,使得每當(dāng)一個對象改變狀態(tài)有滑,則所有依賴于它的對象都會得到通知并被自動更新跃闹。

二、第一個小栗子

舉個小栗子幫助理解。
天氣預(yù)報望艺,一個氣象臺檢測溫度苛秕、氣壓、濕度找默,氣象公司獲得這些數(shù)據(jù)艇劫。氣象臺是被觀察者,氣象公司是觀察者惩激。

//抽象被觀察者
public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObservers();
}

//氣象臺
public class WeatherDataSt implements Subject {
    private float temperatrue;
    private float pressure;
    private float humidity;

    private List<Observer> observers;
    public WeatherDataSt()
    {
        observers=new ArrayList<Observer>();
    }

//注冊觀察者
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);

    }
//刪除觀察者
    @Override
    public void removeObserver(Observer o) {
        if (observers.contains(o)){
            observers.remove(o);
        }
    }
//更新信息通知觀察者
    @Override
    public void notifyObservers() {
        observers.forEach(observer -> observer.update(temperatrue,pressure,humidity));
    }

    public void setData(float temperatrue,float pressure,float humidity){
        this.temperatrue = temperatrue;
        this.pressure = pressure;
        this.humidity = humidity;
    }
}

//抽象觀察者
public interface Observer {
    public void update(float mTemperatrue,float mPressure,float mHumidity);
}

//今日天氣預(yù)報公司
public class CurrentConditions implements Observer {
    private float teperatrue,pressure,humidity;
    @Override
    public void update(float teperatrue, float pressure, float humidity) {
        this.humidity = humidity;
        this.pressure = pressure;
        this.teperatrue = teperatrue;
        display();
    }

    public void display() {
        System.out.println("***Today teperatrue:" + teperatrue + "***");
        System.out.println("***Today pressure:" + pressure + "***");
        System.out.println("***Today humidity:" + humidity + "***");

    }
}

//明日天氣公司
public class ForcastConditions implements Observer{
    private float temperatrue;
    private float pressure;
    private float humidity;
    @Override
    public void update(float temperatrue, float pressure, float humidity) {
        // TODO Auto-generated method stub
        this.temperatrue=temperatrue;
        this.pressure=pressure;
        this.humidity=humidity;

        display();
    }
    public void display()
    {
        System.out.println("**明天溫度:"+(temperatrue+Math.random())+"**");
        System.out.println("**明天氣壓:"+(pressure+10*Math.random())+"**");
        System.out.println("**明天濕度:"+(humidity+Math.random())+"**");
    }
}

//測試
public class WeatherTest {
    public static void main(String[] args) {
        Observer o1 = new CurrentConditions();
        Observer o2 = new ForcastConditions();

        WeatherDataSt subject = new WeatherDataSt();
        subject.registerObserver(o1);
        subject.registerObserver(o2);

        subject.setData(20,30,40);
        subject.notifyObservers();
    }
}

//打拥晟贰:
***Today teperatrue:20.0***
***Today pressure:30.0***
***Today humidity:40.0***
**明天溫度:20.748446673124064**
**明天氣壓:39.210757687486826**
**明天濕度:40.14864883880736**

Subject被觀察者

定義被觀察者必須實現(xiàn)的職責(zé),它必須能夠動態(tài)增加咧欣、取消觀察者浅缸。它一般是抽象類或者是實現(xiàn)類,僅僅完成作為被觀察者必須實現(xiàn)的職責(zé):管理觀察者并通知觀察者魄咕。

Observer觀察者

觀察者接收到消息后,即進行update(更新方法)操作蚌父,接收到的信息進行處理哮兰。

三、第二個栗子

這個栗子原版出自《設(shè)計模式之禪(第二版)》苟弛,李斯和韓非子是師兄弟喝滞,韓非子在韓國,李斯在秦國膏秫,各奉君主右遭,李斯經(jīng)常觀察韓非子的日常。這樣韓非子就是被觀察者缤削,李斯是觀察者窘哈。

//抽象被觀察者
public abstract class  Observable {
    private List<Observer> observers = Collections.synchronizedList(new ArrayList<>());
    public void addObserver(Observer observer){
        this.observers.add(observer);
    }
    public void deleteObserver(Observer observer){
        this.observers.remove(observer);
    }
    public void notifyObservers(String context){
        observers.forEach(observer -> observer.update(context));
    }
}

//韓非子
public class HanFeiZi extends Observable {
    //韓非子要吃飯了
    public void haveBreakfast(){
        super.notifyObservers("韓非子在吃飯");
    }

    //韓非子在娛樂
    public void haveFun(){
        super.notifyObservers("韓非子在娛樂");
    }

}

//抽象觀察者
public interface Observer {
    void update(String context);
}

//李斯
public class Lisi implements Observer {
    @Override
    public void update(String context) {
        System.out.println("李斯:觀察到韓國活動,開始匯報秦始皇亭敢。滚婉。。帅刀。");
        this.reportToQinShiHuang(context);
        System.out.println("李斯報告完畢");
    }

    //匯報給秦始皇
    public void reportToQinShiHuang(String context){
        System.out.println("李斯報告----"+context);
    }
}

//測試
public class DaDian {
    public static void main(String[] args) {
        Observer lisi = new Lisi();
        Observer zhaogao = new ZhaoGao();

        System.out.println("--------------監(jiān)視韓非子---------------");
        HanFeiZi hanFeiZi = new HanFeiZi();

        hanFeiZi.addObserver(lisi);
        hanFeiZi.addObserver(zhaogao);

        hanFeiZi.haveBreakfast();
        hanFeiZi.haveFun();
    }
    
}

打尤酶埂:
--------------監(jiān)視韓非子---------------
李斯:觀察到韓國活動,開始匯報秦始皇扣溺。骇窍。。锥余。
李斯報告----韓非子在吃飯
李斯報告完畢
趙高:觀察到韓國活動腹纳,開始匯報秦始皇。。只估。志群。
趙高報告----韓非子在吃飯
趙高報告完畢
李斯:觀察到韓國活動,開始匯報秦始皇蛔钙。锌云。。吁脱。
李斯報告----韓非子在娛樂
李斯報告完畢
趙高:觀察到韓國活動桑涎,開始匯報秦始皇。兼贡。攻冷。。
趙高報告----韓非子在娛樂
趙高報告完畢

看了兩個栗子遍希,應(yīng)該了解觀察者模式是什么了吧等曼。

觀察者模式的優(yōu)點

  • 觀察者和被觀察者之間是抽象耦合,如此設(shè)計凿蒜,則不管增加觀察者還是被觀察者都非常容易擴展禁谦。
  • 建立一套觸發(fā)機制。

如果秦始皇不太相信李斯废封,就讓趙高也去觀察州泊,并且同時觀察韓國君的行動。

增加被觀察者:韓國君
public class HanGuoJun extends Observable {

    public void  sleep(){
        super.notifyObservers("韓國君睡覺了");
    }
}
增加觀察者:趙高
public class ZhaoGao implements Observer {
    @Override
    public void update(String context) {
        System.out.println("趙高:觀察到韓國活動漂洋,開始匯報秦始皇遥皂。。刽漂。演训。");
        this.reportToQinShiHuang(context);
        System.out.println("趙高報告完畢");
    }

    //匯報給秦始皇
    public void reportToQinShiHuang(String context){
        System.out.println("趙高報告----"+context);
    }
}
測試:
public class DaDian {
    public static void main(String[] args) {
        Observer lisi = new Lisi();
        Observer zhaogao = new ZhaoGao();

        System.out.println("--------------監(jiān)視韓非子---------------");
        HanFeiZi hanFeiZi = new HanFeiZi();

        hanFeiZi.addObserver(lisi);
        hanFeiZi.addObserver(zhaogao);

        hanFeiZi.haveBreakfast();
        hanFeiZi.haveFun();

        System.out.println("--------------監(jiān)視韓國君---------------");

        HanGuoJun hanGuoJun = new HanGuoJun();
        hanGuoJun.addObserver(zhaogao);
        hanGuoJun.sleep();
    }
}

觀察者模式的缺點

觀察者模式需要考慮一下開發(fā)效率和運行效率問題,一個被觀察者爽冕,多個觀察者仇祭,開發(fā)和調(diào)試就會比較負(fù)責(zé),而且在java中消息的通知默認(rèn)是順序執(zhí)行颈畸,一個觀察者卡殼乌奇,會影響整體的執(zhí)行效率。這種情況下眯娱,一般考慮采用異步的方式礁苗。

多級觸發(fā)的效率更是令人擔(dān)憂

使用場景

  • 關(guān)聯(lián)行為場景。需要注意的是徙缴,關(guān)聯(lián)行為是可拆分的试伙,而不是“組合”關(guān)系嘁信。
  • 事件多級觸發(fā)場景。
  • 跨系統(tǒng)的消息交換場景疏叨,如消息隊列的處理機制潘靖。

注意事項

廣播鏈的問題

一個觀察者可以有雙重身份,既是觀察者蚤蔓,也是被觀察者卦溢,這樣廣播鏈就比較復(fù)雜了,可維護性非常差秀又,
根據(jù)經(jīng)驗建議单寂,在一個觀察者模式中最多出現(xiàn)一個對象既是觀察者也是被觀察者,也就是說消息最多轉(zhuǎn)發(fā)一次吐辙。
注意 它和責(zé)任鏈模式的最大區(qū)別就是觀察者廣播鏈在傳播的過程中消息是隨時改變的宣决,它是由相鄰的兩個節(jié)點協(xié)商的消息結(jié)構(gòu);
而責(zé)任鏈模式在消息傳遞過程中基本上保持消息不可變昏苏,如果改變也只是在原有的消息上進行修正尊沸。

異步處理問題

被觀察者發(fā)生動作了,觀察者要做出回應(yīng)捷雕,如果觀察者比較多椒丧,而且處理時間比較長怎么辦?那就應(yīng)異步唄救巷,異步處理就要考慮線程安全和隊列的問題。

四句柠、java內(nèi)置觀察者和被觀察者

java內(nèi)部為我們提供了兩個類浦译,觀察者(java.util.Observable)是個類,被觀察者(java.util.Observer)是個接口。

看下源碼:

java.util.Observable
package java.util;
public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;


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

    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

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

    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();
    }


    protected synchronized void setChanged() {
        changed = true;
    }


    protected synchronized void clearChanged() {
        changed = false;
    }

   
    public synchronized boolean hasChanged() {
        return changed;
    }

    public synchronized int countObservers() {
        return obs.size();
    }
}

java.util.Observer
package java.util;

public interface Observer {
    void update(Observable o, Object arg);
}

從源碼看出java.util.Observable里的方法基本都是同步方法溯职,線程安全的精盅。

通過java內(nèi)置的設(shè)計模式實現(xiàn)第二個栗子

public class Hanfeizi extends Observable {
    public void haveBreakfast(){
        super.setChanged();
        super.notifyObservers("韓非子在吃飯");
    }
}

public class Hanguojun extends Observable {
    public void sleep(){
        super.setChanged();
        super.notifyObservers("韓國君在睡覺");
    }
}


public class Lisi implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        System.out.println(o);
        this.reportToQinShiHuang(o,arg);
    }

    //匯報給秦始皇
    public void reportToQinShiHuang(Observable o,Object arg){
        System.out.println("李斯報告----"+arg.toString());
    }
}

public class test {
    public static void main(String[] args) {
        Lisi lisi = new Lisi();
        Hanfeizi hanfeizi = new Hanfeizi();
        hanfeizi.addObserver(lisi);
        hanfeizi.haveBreakfast();

        Hanguojun hanguojun = new Hanguojun();
        hanguojun.addObserver(lisi);
        hanguojun.sleep();
    }
}

打印:

javaInDemo.Hanfeizi@4554617c
李斯報告----韓非子在吃飯
javaInDemo.Hanguojun@74a14482
李斯報告----韓國君在睡覺

項目中真實的觀察者模式

為什么說“真實”呢谜酒?因為我們剛剛講的那些事太標(biāo)準(zhǔn)的模式了叹俏,在系統(tǒng)設(shè)計中會對觀察者模式進行改造或者改裝,主要在以下2個方面:

1僻族、觀察者和被觀察者之間的消息溝通:

觀察者和被觀察者之間的消息溝通粘驰,被觀察者狀態(tài)改變會觸發(fā)觀察者的一個行為,同時會傳遞一個消息給觀察者述么,這是正確的蝌数,在實際中一般的做法是:觀察者中的update方法接受兩個參數(shù),一個是被觀察者度秘,一個是DTO(Data Transfer Object顶伞,據(jù)傳輸對象),DTO一般是一個純潔的JavaBean,由被觀察者生成唆貌,由觀察者消費滑潘。

當(dāng)然,如果考慮到遠(yuǎn)程傳輸锨咙,一般消息是以XML格式傳遞语卤。

2、觀察者響應(yīng)方式:

觀察者一個比較復(fù)雜的邏輯蓖租,它要接受被觀察者傳遞過來的信息粱侣,同時還要對他們進行邏輯處理,在一個觀察者多個被觀察者的情況下蓖宦,性能就需要提到日程上來考慮了齐婴,為什么呢?如果觀察者來不及響應(yīng)稠茂,被觀察者的執(zhí)行時間是不是也會被拉長柠偶?那現(xiàn)在的問題就是:觀察者如何快速響應(yīng)?有兩個辦法:

一是采用多線程技術(shù)睬关,甭管是被觀察者啟動線程還是觀察者啟動線程诱担,都可以明顯地提高系統(tǒng)性能,這也就是大家通常所說的異步架構(gòu)电爹。

二是緩存技術(shù)蔫仙,甭管你誰來,我已經(jīng)準(zhǔn)備了足夠的資源給你了丐箩,我保證快速響應(yīng)摇邦,這當(dāng)然也是一種比較好方案,代價就是開發(fā)難度很大屎勘,而且壓力測試要做的足夠充分施籍,這種方案也就是大家說的同步架構(gòu)。

雖然java給我提供了觀察者模式的實現(xiàn)概漱,但是實際開發(fā)中不一定能滿足丑慎,就比如Observable,這個類是具體類瓤摧,只要是實現(xiàn)觀察者就必須繼承他竿裂,倘若這個實現(xiàn)觀察者類還要繼承一個類,那就沒辦法了姻灶,java只能是單繼承铛绰,這就增加了耦合。

項目的架構(gòu)講究的是高內(nèi)聚,低耦合

本文講解的觀察者過于淺顯,舉的栗子也不太符合實際運用陷谱。真正的理解還是要實際開發(fā)中去融匯貫通这嚣。

源碼地址:
https://gitee.com/stefanpy/DesignPattern

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸥昏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子姐帚,更是在濱河造成了極大的恐慌吏垮,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罐旗,死亡現(xiàn)場離奇詭異膳汪,居然都是意外死亡,警方通過查閱死者的電腦和手機九秀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進店門遗嗽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人鼓蜒,你說我怎么就攤上這事痹换。” “怎么了都弹?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵娇豫,是天一觀的道長。 經(jīng)常有香客問我畅厢,道長冯痢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任框杜,我火速辦了婚禮系羞,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘霸琴。我一直安慰自己,他們只是感情好昭伸,可當(dāng)我...
    茶點故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布梧乘。 她就那樣靜靜地躺著,像睡著了一般庐杨。 火紅的嫁衣襯著肌膚如雪选调。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天灵份,我揣著相機與錄音仁堪,去河邊找鬼。 笑死填渠,一個胖子當(dāng)著我的面吹牛弦聂,可吹牛的內(nèi)容都是我干的鸟辅。 我是一名探鬼主播,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼莺葫,長吁一口氣:“原來是場噩夢啊……” “哼匪凉!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起捺檬,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤再层,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后堡纬,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體聂受,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年烤镐,在試婚紗的時候發(fā)現(xiàn)自己被綠了蛋济。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,992評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡职车,死狀恐怖瘫俊,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情悴灵,我是刑警寧澤扛芽,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站积瞒,受9級特大地震影響川尖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜茫孔,卻給世界環(huán)境...
    茶點故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一叮喳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧缰贝,春花似錦馍悟、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至赞弥,卻和暖如春毅整,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背绽左。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工悼嫉, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人拼窥。 一個月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓戏蔑,卻偏偏與公主長得像蹋凝,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子辛臊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,947評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理仙粱,服務(wù)發(fā)現(xiàn),斷路器彻舰,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • *本篇文章已授權(quán)微信公眾號 guolin_blog (郭霖)獨家發(fā)布 寫在前面 最近在學(xué)習(xí) EventBus 和 ...
    Marker_Sky閱讀 1,176評論 2 8
  • 【學(xué)習(xí)難度:★★★☆☆伐割,使用頻率:★★★★★】直接出處:觀察者模式梳理和學(xué)習(xí):https://github.com...
    BruceOuyang閱讀 1,529評論 1 5
  • 最近對講歷史的書恢復(fù)了興趣。尤其是個人寫的歷史評論刃唤。 隨著年齡的增長隔心,生活經(jīng)歷的增多,我發(fā)現(xiàn)自己對人性的把握卻越來...
    任立春閱讀 230評論 0 0
  • 一周五天尚胞,他有三天遲到硬霍,其他兩天打車上班勉強趕上公司的打卡時間。 你要問他為什么笼裳? 他會一本正經(jīng)地說:“起不來唯卖。”...
    bubble14閱讀 246評論 0 2