設(shè)計(jì)模式之觀察者模式

轉(zhuǎn)載請(qǐng)注明出處:http://www.reibang.com/p/d55ee6e83d66
文章中的例子和思路均來自于《Head First》

剛剛開通了微信公眾號(hào):BaronTalk滋恬,之前專欄上的文章也陸續(xù)完成了搬遷。后續(xù)會(huì)持續(xù)保質(zhì)保量的輸出,還在等什么?降宅!關(guān)注一波吧R涑!晚顷! :-)


場(chǎng)景

我們接到一個(gè)來自氣象局的需求:氣象局需要我們構(gòu)建一套系統(tǒng),這系統(tǒng)有兩個(gè)公告牌疗疟,分別用于顯示當(dāng)前的實(shí)時(shí)天氣和未來幾天的天氣預(yù)報(bào)该默。當(dāng)氣象局發(fā)布新的天氣數(shù)據(jù)(WeatherData)后,兩個(gè)公告牌上顯示的天氣數(shù)據(jù)必須實(shí)時(shí)更新策彤。氣象局同時(shí)要求我們保證程序擁有足夠的可擴(kuò)展性栓袖,因?yàn)楹笃陔S時(shí)可能要新增新的公告牌。

概況

這套系統(tǒng)中主要包括三個(gè)部分:氣象站(獲取天氣數(shù)據(jù)的物理設(shè)備)店诗、WeatherData(追蹤來自氣象站的數(shù)據(jù)裹刮,并更新公告牌)、公告牌(用于展示天氣數(shù)據(jù))


WeatherStation

WeatherData知道如何跟氣象站聯(lián)系庞瘸,以獲得天氣數(shù)據(jù)捧弃。當(dāng)天氣數(shù)據(jù)有更新時(shí),WeatherData會(huì)更新兩個(gè)公告牌用于展示新的天氣數(shù)據(jù)擦囊。

錯(cuò)誤示范

我們先來看看隔壁老王的實(shí)現(xiàn):

public class WeatherData {

    //實(shí)例變量聲明
    ...
    
    public void measurementsChanged() {
    
        float temperature = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();
        List<Float> forecastTemperatures = getForecastTemperatures();
        
        //更新公告牌
        currentConditionsDisplay.update(temperature, humidity, pressure);
        forecastDisplay.update(forecastTemperatures);
    }
    ...
}

上面這段代碼是典型的針對(duì)實(shí)現(xiàn)編程塔橡,這會(huì)導(dǎo)致我們以后增加或刪除公告牌時(shí)必須修改程序梅割。我們現(xiàn)在來看看觀察者模式,然后再回來看看如何將觀察者模式應(yīng)用到這個(gè)程序葛家。

觀察者模式介紹

觀察者模式面向的需求是:A對(duì)象(觀察者)對(duì)B對(duì)象(被觀察者)的某種變化高度敏感户辞,需要在B變化的一瞬間做出反應(yīng)。舉個(gè)例子癞谒,新聞里喜聞樂見的警察抓小偷底燎,警察需要在小偷伸手作案的時(shí)候?qū)嵤┳ゲ丁T谶@個(gè)例子里弹砚,警察是觀察者双仍、小偷是被觀察者,警察需要時(shí)刻盯著小偷的一舉一動(dòng)桌吃,才能保證不會(huì)錯(cuò)過任何瞬間朱沃。程序里的觀察者和這種真正的【觀察】略有不同,觀察者不需要時(shí)刻盯著被觀察者(例如A不需要每隔1ms就檢查一次B的狀態(tài))茅诱,二是采用注冊(cè)(Register)或者成為訂閱(Subscribe)的方式告訴被觀察者:我需要你的某某狀態(tài)逗物,你要在它變化時(shí)通知我。采取這樣被動(dòng)的觀察方式瑟俭,既省去了反復(fù)檢索狀態(tài)的資源消耗翎卓,也能夠得到最高的反饋速度。

觀察者模式通嘲诩模基于SubjectObserver接口類來設(shè)計(jì)失暴,下面是是類圖:

Observer

觀察者模式的應(yīng)用

結(jié)合上面的類圖,我們現(xiàn)在將觀察者模式應(yīng)用到WeatherData項(xiàng)目中來微饥。于是有了下面這張類圖:


ObserverForWeatherStation

主題接口

/**
* 主題(發(fā)布者逗扒、被觀察者)
*/
public interface Subject {

    /**
     * 注冊(cè)觀察者
     */
    void registerObserver(Observer observer);

    /**
     * 移除觀察者
     */
    void removeObserver(Observer observer);

    /**
     * 通知觀察者
     */
    void notifyObservers(); 
}

觀察者接口

/**
 * 觀察者
 */
public interface Observer {
    void update();
}

公告牌用于顯示的公共接口

public interface DisplayElement {
    void display();
}

下面我們?cè)賮砜纯碬eatherData是如何實(shí)現(xiàn)的

public class WeatherData implements Subject {

    private List<Observer> observers;

    private float temperature;//溫度
    private float humidity;//濕度
    private float pressure;//氣壓
    private List<Float> forecastTemperatures;//未來幾天的溫度

    public WeatherData() {
        this.observers = new ArrayList<Observer>();
    }

    @Override
    public void registerObserver(Observer observer) {
        this.observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        this.observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update();
        }
    }

    public void measurementsChanged() {
        notifyObservers();
    }

    public void setMeasurements(float temperature, float humidity, 
    float pressure, List<Float> forecastTemperatures) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        this.forecastTemperatures = forecastTemperatures;
        measurementsChanged();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }

    public List<Float> getForecastTemperatures() {
        return forecastTemperatures;
    }
}

顯示當(dāng)前天氣的公告牌CurrentConditionsDisplay

public class CurrentConditionsDisplay implements Observer, DisplayElement {

    private WeatherData weatherData;

    private float temperature;//溫度
    private float humidity;//濕度
    private float pressure;//氣壓

    public CurrentConditionsDisplay(WeatherData weatherData) {
        this.weatherData = weatherData;
        this.weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("當(dāng)前溫度為:" + this.temperature + "℃");
        System.out.println("當(dāng)前濕度為:" + this.humidity);
        System.out.println("當(dāng)前氣壓為:" + this.pressure);
    }

    @Override
    public void update() {
        this.temperature = this.weatherData.getTemperature();
        this.humidity = this.weatherData.getHumidity();
        this.pressure = this.weatherData.getPressure();
        display();
    }
}

顯示未來幾天天氣的公告牌ForecastDisplay

public class ForecastDisplay implements Observer, DisplayElement {

    private WeatherData weatherData;

    private List<Float> forecastTemperatures;//未來幾天的溫度

    public ForecastDisplay(WeatherData weatherData) {
        this.weatherData = weatherData;
        this.weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("未來幾天的氣溫");
        int count = forecastTemperatures.size();
        for (int i = 0; i < count; i++) {
            System.out.println("第" + i + "天:" + forecastTemperatures.get(i) + "℃");
        }
    }

    @Override
    public void update() {
        this.forecastTemperatures = this.weatherData.getForecastTemperatures();
        display();
    }
}

到這里,我們整個(gè)氣象局的WeatherData應(yīng)用就改造完成了欠橘。兩個(gè)公告牌CurrentConditionsDisplayForecastDisplay實(shí)現(xiàn)了ObserverDisplayElement接口缴阎,在他們的構(gòu)造方法中會(huì)調(diào)用WeatherDataregisterObserver方法將自己注冊(cè)成觀察者,這樣被觀察者WeatherData就會(huì)持有觀察者的應(yīng)用简软,并將它們保存到一個(gè)集合中。當(dāng)被觀察者``WeatherData狀態(tài)發(fā)送變化時(shí)就會(huì)遍歷這個(gè)集合述暂,循環(huán)調(diào)用觀察者公告牌更新數(shù)據(jù)的方法痹升。后面如果我們需要增加或者刪除公告牌就只需要新增或者刪除實(shí)現(xiàn)了ObserverDisplayElement`接口的公告牌就好了。

觀察者模式將觀察者和主題(被觀察者)徹底解耦畦韭,主題只知道觀察者實(shí)現(xiàn)了某一接口(也就是Observer接口)疼蛾。并不需要觀察者的具體類是誰、做了些什么或者其他任何細(xì)節(jié)艺配。任何時(shí)候我們都可以增加新的觀察者察郁。因?yàn)橹黝}唯一依賴的東西是一個(gè)實(shí)現(xiàn)了Observer接口的對(duì)象列表衍慎。

好了,我們測(cè)試下利用觀察者模式重構(gòu)后的程序:

public class ObserverPatternTest {

    public static void main(String[] args) {

        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
        ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

        List<Float> forecastTemperatures = new ArrayList<Float>();
        forecastTemperatures.add(22f);
        forecastTemperatures.add(-1f);
        forecastTemperatures.add(9f);
        forecastTemperatures.add(23f);
        forecastTemperatures.add(27f);
        forecastTemperatures.add(30f);
        forecastTemperatures.add(10f);
        weatherData.setMeasurements(22f, 0.8f, 1.2f, forecastTemperatures);
    }
}

輸出結(jié)果:

當(dāng)前溫度為:22.0℃
當(dāng)前濕度為:0.8
當(dāng)前氣壓為:1.2
未來幾天的氣溫
第0天:22.0℃
第1天:-1.0℃
第2天:9.0℃
第3天:23.0℃
第4天:27.0℃
第5天:30.0℃
第6天:10.0℃

源碼地址:
https://github.com/BaronZ88/DesignPatterns/tree/master/src/com/baron/patterns/observer

如果你喜歡我的文章皮钠,就關(guān)注下我的公眾號(hào) BaronTalk 稳捆、 知乎專欄 或者在 GitHub 上添個(gè) Star 吧!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末麦轰,一起剝皮案震驚了整個(gè)濱河市乔夯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌款侵,老刑警劉巖末荐,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異新锈,居然都是意外死亡甲脏,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門妹笆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來块请,“玉大人,你說我怎么就攤上這事晾浴「合纾” “怎么了?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵脊凰,是天一觀的道長(zhǎng)抖棘。 經(jīng)常有香客問我,道長(zhǎng)狸涌,這世上最難降的妖魔是什么切省? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮帕胆,結(jié)果婚禮上朝捆,老公的妹妹穿的比我還像新娘。我一直安慰自己懒豹,他們只是感情好芙盘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著脸秽,像睡著了一般儒老。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上记餐,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天驮樊,我揣著相機(jī)與錄音,去河邊找鬼。 笑死囚衔,一個(gè)胖子當(dāng)著我的面吹牛挖腰,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播练湿,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼猴仑,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了鞠鲜?” 一聲冷哼從身側(cè)響起宁脊,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎贤姆,沒想到半個(gè)月后榆苞,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡霞捡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年坐漏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片碧信。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赊琳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出砰碴,到底是詐尸還是另有隱情躏筏,我是刑警寧澤,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布呈枉,位于F島的核電站趁尼,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏猖辫。R本人自食惡果不足惜酥泞,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望啃憎。 院中可真熱鬧芝囤,春花似錦、人聲如沸辛萍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽贩毕。三九已至悯许,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間耳幢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留睛藻,地道東北人启上。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像店印,于是被迫代替她去往敵國(guó)和親冈在。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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