Head First 設(shè)計(jì)模式(2)---觀察者(Observer)模式

本文參照《Head First 設(shè)計(jì)模式》,轉(zhuǎn)載請(qǐng)注明出處
對(duì)于整個(gè)系列,我們按照這本書的設(shè)計(jì)邏輯怒允,使用情景分析的方式來描述,并且穿插使用一些問題锈遥,總結(jié)的方式來講述误算。并且所有的開發(fā)源碼仰美,都會(huì)托管到github上。
項(xiàng)目地址:https://github.com/jixiang52002/HFDesignPattern

前一章主要講解了設(shè)計(jì)模式入門和最常用的一個(gè)模式-----策略模式儿礼,并結(jié)合Joe的鴨子模型進(jìn)行分析咖杂,想要了解的朋友可以回去回看一下。Head First 設(shè)計(jì)模式(1)-----策略模式
這里我們將繼續(xù)介紹一種可以幫助對(duì)象知悉現(xiàn)狀蚊夫,不會(huì)錯(cuò)過該對(duì)象感興趣的事诉字。甚至對(duì)象可以自己決定是都要繼續(xù)接受通知。有過設(shè)計(jì)模式學(xué)習(xí)經(jīng)驗(yàn)的人會(huì)脫口而出-----觀察者模式知纷。對(duì)的壤圃,接下來我們將了解一個(gè)新的設(shè)計(jì)模式,也就是觀察者模式琅轧。

1.引言

最近你的團(tuán)隊(duì)獲取了一個(gè)新的合約伍绳,需要負(fù)責(zé)建立一個(gè)Weather-O-Rama公司的下一代氣象站----Internet氣象觀測(cè)站。
合約內(nèi)容如下:

恭喜貴公司獲選為敝公司建立下一代Internet氣象觀測(cè)站乍桂!該氣象站必須建立在我們專利申請(qǐng)的WeatherData對(duì)象上冲杀,由WeatherData對(duì)象負(fù)責(zé)追蹤目前的天氣狀況(溫度、濕度睹酌、氣壓)权谁。我們希望貴公司能建立一個(gè)應(yīng)用,有三種布告板憋沿,分別顯示目前的狀況旺芽、氣象統(tǒng)計(jì)及簡單的預(yù)報(bào)。當(dāng)WeatherData對(duì)象獲取到最新的測(cè)量數(shù)據(jù)時(shí)辐啄,三種布告板必須實(shí)時(shí)更新采章。
而且,這是一個(gè)可以拓展的氣象站壶辜,Weather-O-Rama氣象站希望公布一組API悯舟,讓其他開發(fā)人員可以寫出自己的氣象布告板,并插入此應(yīng)用中我們希望貴公司可以提供這樣的API士复。
Weather-O-Rama氣象站有很好的商業(yè)運(yùn)營模式:一旦客戶上鉤,他們使用每個(gè)布告板都要付錢最好的部分就是翩活,為了感謝貴公司建立此系統(tǒng)阱洪,我們將以公司的認(rèn)股權(quán)支付你。
我們期待看到你的設(shè)計(jì)和應(yīng)用的alpha版本菠镇。
附注:我們正在通宵整理WeatherData源文件給你們冗荸。

1.1需求分析

根據(jù)開發(fā)的經(jīng)驗(yàn),我們首先分析Weather-O-Rama公司的需求:

  • 此系統(tǒng)有三個(gè)部分組成:氣象站(獲取實(shí)際的氣象數(shù)據(jù)的物理組成)利耍,WeatherData對(duì)象(追蹤來自氣象站的數(shù)據(jù)蚌本,并更新布告板)和布告板(顯示目前天氣狀況展示給用戶)
  • 項(xiàng)目應(yīng)用中盔粹,開發(fā)者需要利用WeatherData去實(shí)時(shí)獲取氣象數(shù)據(jù),并且更新三個(gè)布告板:目前氣象程癌,氣象統(tǒng)計(jì)和天氣預(yù)報(bào)舷嗡。
  • 系統(tǒng)必須具備很高的可拓展性,讓其他的開發(fā)人員可以建立定制的布告板嵌莉,用戶可以隨心所欲地添加或刪除任何布告板进萄。

我們初始設(shè)計(jì)結(jié)構(gòu)如下:


初始設(shè)計(jì)結(jié)構(gòu)

1.2WeatherData類

第二天,Weather-O-Rama公司發(fā)送過來WeatherData的源碼锐峭,其結(jié)構(gòu)如下圖


WeatherData數(shù)據(jù)結(jié)構(gòu)

其中measurementsChanged()方法在氣象測(cè)試更新時(shí)中鼠,被調(diào)用。

1.3錯(cuò)誤的編碼方式

首先沿癞,我們從大部分不懂設(shè)計(jì)模式的開發(fā)者常用的設(shè)計(jì)方式開始援雇。
根據(jù)Weather-O-Rama氣象站開發(fā)人員的需求暗示,在measurementsChanged()方法中添加相關(guān)的代碼:

public class WeatherData {
    private float temperature;//溫度
    private float humidity;//濕度
    private float pressure;//氣壓
    
    private CurrentConditionsDisplay currentConditionsDisplay;//目前狀態(tài)布告板
    private StatisticsDisplay statisticsDisplay;//統(tǒng)計(jì)布告板
    private ForecastDisplay forecastDisplay;//預(yù)測(cè)布告板
    
    public WeatherData(CurrentConditionsDisplay currentConditionsDisplay
            ,StatisticsDisplay statisticsDisplay
            ,ForecastDisplay forecastDisplay){
        this.currentConditionsDisplay=currentConditionsDisplay;
        this.statisticsDisplay=statisticsDisplay;
        this.forecastDisplay=forecastDisplay;
    }
    
    
    public float getTemperature() {
        return temperature;
    }
    
    public float getHumidity(){
        return humidity;
    }
    
    public float getPressure(){
        return pressure;
    }

    //實(shí)例變量聲明
    public  void measurementsChanged(){
        //調(diào)用WeatherData的三個(gè)getter方法獲取最近的測(cè)量值
        float temp=getTemperature();
        float humidity=getHumidity();
        float pressure=getPressure();
        
        currentConditionsDisplay.update(temp,humidity,pressure);
        statisticsDisplay.update(temp,humidity,pressure);
        forecastDisplay.update(temp,humidity,pressure);
    }
    
    //通知發(fā)生變化
    public void setMeasurements(float temperature,float humidity,float pressure){
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        measurementsChanged();
    }
}

回顧第一章的三個(gè)設(shè)計(jì)原則椎扬,我們發(fā)現(xiàn)這里違反了幾個(gè)原則

第一設(shè)計(jì)原則
找出應(yīng)用中可能需要變化之處惫搏,把它們獨(dú)立出來,不要和那些不需要變化的代碼混合在一起晶府。

第二設(shè)計(jì)原則
針對(duì)于接口編程钻趋,不針對(duì)實(shí)現(xiàn)編程

第三設(shè)計(jì)原則
多用組合,少用繼承

在這里我們使用了針對(duì)實(shí)現(xiàn)編程蛮位,并且沒有將變化部分獨(dú)立出來较沪,這樣會(huì)導(dǎo)致我們以后在增加或刪除布告板時(shí)必須修改應(yīng)用程序失仁。而且,最重要的是萄焦,我們犧牲了可拓展性控轿。


分析錯(cuò)誤點(diǎn)

既然這里我們提到了要使用觀察者模式來解決問題拂封,那么該如何下手。并且冒签,什么是觀察者模式萧恕?

2.觀察者模式

2.1認(rèn)識(shí)觀察者模式

為了方便理解肠阱,我們從日常生活中常遇到的情形來理解觀察者模式朴读,這里我們使用生活常見的報(bào)紙和雜志訂閱業(yè)務(wù)邏輯來理解:

  • 報(bào)社的業(yè)務(wù)在于出版報(bào)紙
  • 訂閱報(bào)紙的用戶,只要對(duì)應(yīng)報(bào)社有新的報(bào)紙出版缘回,就會(huì)給你送來
  • 當(dāng)用戶不想繼續(xù)訂閱報(bào)紙典挑,可以直接取消訂閱。那么之后就算有新的報(bào)紙出版拙寡,也不會(huì)送給對(duì)應(yīng)用戶了琳水。
  • 只要報(bào)社一直存在,任何用戶都可以自由訂閱或取消訂閱報(bào)紙

從上面的邏輯我們分析出诚啃,這里由以下部分組成私沮,報(bào)社,用戶造垛,訂閱晰搀。將其抽象出來就i是:出版者,訂閱者杆逗,訂閱鳞疲。這里觀察者模式的雛形已經(jīng)出來了。

出版者+訂閱者=觀察者模式

如果上面已經(jīng)理解了報(bào)社報(bào)紙訂閱的邏輯排龄,也可以很快知道觀察者模式是什么翎朱。只是在其中名稱會(huì)有差異,前面提到的“出版者”我們可以稱為“主題(Subject)”“被觀察者(Observable)”(后一個(gè)更加常用)拴曲,“訂閱者”我們稱為“觀察者(Observer)”澈灼,這里我們采用類UML的結(jié)構(gòu)圖來解釋:

觀察者模式結(jié)構(gòu)圖

2.2 觀察者模式注冊(cè)/取消注冊(cè)

場(chǎng)景1:
某一天,鴨子對(duì)象覺得自己的朋友都訂閱了主題委乌,自己也想稱為一個(gè)觀察者荣回。于是告訴主題遭贸,它想當(dāng)一個(gè)觀察者壕吹。完成訂閱后删铃,鴨子也成為一個(gè)觀察者了。


鴨子成為觀察者后的結(jié)構(gòu)圖

這樣當(dāng)主題數(shù)據(jù)發(fā)生變化時(shí)咒劲,鴨子對(duì)象也可以得到通知了!缎患!

場(chǎng)景2:
老鼠對(duì)象厭煩了每天都被主題煩挤渔,決定從觀察者序列離開风题,于是它告訴主題它想離開觀察者行列,主題將它從觀察者中除名眼刃。


老鼠離開觀察者后的結(jié)構(gòu)圖

之后主題數(shù)據(jù)發(fā)生變化時(shí)摇肌,不會(huì)再通知老鼠對(duì)象围小。

上面的兩個(gè)情形分別對(duì)應(yīng)了注冊(cè)和取消注冊(cè)树碱,這也是觀察者模式最重要的兩個(gè)概念变秦。注冊(cè)后的對(duì)象我們才可以稱為觀察者。觀察者取消注冊(cè)后也不能稱為觀察者赎婚。

2.3 觀察者模式定義

通過報(bào)紙業(yè)務(wù)和對(duì)象訂閱的例子樱溉,我們可以勾勒出觀察者模式的基本概念。

觀察者模式定義了對(duì)象之間的一對(duì)多的依賴歧焦,這樣一來肚医,當(dāng)一個(gè)對(duì)象改變狀態(tài)時(shí)肠套,它所有的依賴者都會(huì)收到通知并自動(dòng)更新。

主題/被觀察者和觀察者之間定義了一對(duì)多的關(guān)系瓷耙。觀察者依賴于主題/被觀察者刁赖。一旦主題/被觀察者數(shù)據(jù)發(fā)生改變的時(shí)候宇弛,觀察者就會(huì)收到通知。那么彻况,如何實(shí)現(xiàn)觀察者和主題/被觀察者呢舅踪?

2.4 觀察者模式實(shí)現(xiàn)

由于網(wǎng)絡(luò)上的實(shí)現(xiàn)觀察者的方式非常多抽碌,我們這里采取比較容易理解的方式Subject和Observer。對(duì)于更高級(jí)的使用方式左权,可以百度。
接下來我們來看看基于Subject和Observer的類圖結(jié)構(gòu):


Subject和Observer的類圖結(jié)構(gòu)

3. 設(shè)計(jì)氣象站

到這里我們?cè)倩氐疆?dāng)初的問題,氣象站中結(jié)構(gòu)模型為一對(duì)多模型瀑梗,其中WeatherData為氣象模型中的“一”抛丽,而“多”也就對(duì)應(yīng)了這里用來展示天氣監(jiān)測(cè)數(shù)據(jù)的各種布告板饰豺。相對(duì)于之前的針對(duì)實(shí)現(xiàn)的方式冤吨,使用觀察者模式來設(shè)計(jì)會(huì)更加符合需求。優(yōu)先我們給出新的氣象站模型垒探。


氣象站數(shù)據(jù)模型

3.1實(shí)現(xiàn)氣象站

依照前面的設(shè)計(jì)結(jié)構(gòu)圖怠李,最終來實(shí)現(xiàn)具體代碼結(jié)構(gòu)

1.Subject

public interface Subject {

    //注冊(cè)觀察者
    public void registerObserver(Observer o);
    
    //刪除觀察者
    public void removeObserver(Observer o);
    
    //當(dāng)主題發(fā)生數(shù)據(jù)變化時(shí)捺癞,通知所有觀察
    public void notifyObservers();
    
}

2.Observer

public interface Observer {

   /**
    * 
    * update:當(dāng)氣象站的觀測(cè)數(shù)據(jù)發(fā)生改變時(shí),這個(gè)方法會(huì)被調(diào)用
    * @param temp 溫度
    * @param hunmidity 濕度
    * @param pressure  氣壓
    * @since JDK 1.6
    */
    public void update(float temp,float hunmidity,float pressure);
}

3.DisplayElement

public interface DisplayElement {
    //當(dāng)布告板需要展示時(shí)惕鼓,調(diào)用此方法時(shí)
    public void display();
}

4.新的WeatherData1

public class WeatherData1 implements Subject{
    
    private ArrayList<Observer> observers;
    
    private float temperature;
    
    private float humiditty;
    
    private float pressure;
    
    public WeatherData1(){
        observers=new ArrayList<Observer>();
    }

   //注冊(cè)
    public void registerObserver(Observer o) {
        observers.add(o);
        
    }

    //刪除
    public void removeObserver(Observer o) {
       int i=observers.indexOf(o);
       if(i>=0){
           observers.remove(i);
       }
        
    }

    //通知觀察者數(shù)據(jù)變化
    public void notifyObservers() {
        for(int i=0;i<observers.size();i++){
            Observer observer=observers.get(i);
            observer.update(temperature, humiditty, pressure);
        }
        
    }
    
    public void measurementsChanged(){
        notifyObservers();
    }
    
    public void setMeasurements(float temperature,float humidity,float pressure){
        this.temperature=temperature;
        this.humiditty=humidity;
        this.pressure=pressure;
        measurementsChanged();
    }

}

5.CurrentConditionsDisplay

public class CurrentConditionsDisplay implements Observer,DisplayElement{
    
    private float temperature;
    private float humidity;
    private float pressure;
    private Subject weatherData;
    
    public CurrentConditionsDisplay(Subject weatherData){
        this.weatherData=weatherData;
        weatherData.registerObserver(this);
    }
    
    /**
     * 
     * update:更新布告板內(nèi)容
     * @author 吉祥
     * @param temperature
     * @param humidity
     * @param pressure
     * @since JDK 1.6
     */
    public void update(float temperature,float humidity,float pressure){
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        display();
    }

    /**
     * 
     * display:展示布告板內(nèi)容
     * @author 吉祥
     * @since JDK 1.6
     */
    public void display(){
        System.out.println("Current conditons:"+temperature
                +"F degrees and "+humidity+"% humidity");
    }
}

6.ForecastDisplay

public class ForecastDisplay implements Observer,DisplayElement{
    private float temperature;
    private float humidity;
    private float pressure;
    private Subject weatherData;
    
    public ForecastDisplay(Subject weatherData){
        this.weatherData=weatherData;
        weatherData.registerObserver(this);
    }
    
    /**
     * 
     * update:更新布告板內(nèi)容
     * @author 吉祥
     * @param temperature
     * @param humidity
     * @param pressure
     * @since JDK 1.6
     */
    public void update(float temperature,float humidity,float pressure){
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        display();
    }

    /**
     * 
     * display:展示布告板內(nèi)容
     * @author 吉祥
     * @since JDK 1.6
     */
    public void display(){
        System.out.println("Forecast: More of the same");
    }

}

7.StatisticsDisplay

public class StatisticsDisplay implements Observer,DisplayElement{
    private float temperature;
    private float humidity;
    private float pressure;
    private Subject weatherData;
    
    public StatisticsDisplay(Subject weatherData){
        this.weatherData=weatherData;
        weatherData.registerObserver(this);
    }
    
    /**
     * 
     * update:更新布告板內(nèi)容
     * @author 吉祥
     * @param temperature
     * @param humidity
     * @param pressure
     * @since JDK 1.6
     */
    public void update(float temperature,float humidity,float pressure){
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        display();
    }

    /**
     * 
     * display:展示布告板內(nèi)容
     * @author 吉祥
     * @since JDK 1.6
     */
    public void display(){
        System.out.println("Avg/Max/Min temperature= "+temperature
                +"/"+temperature+"/"+temperature);
    }

}

ps:這里在Observer中使用Subject原因在于方便以后的取消注冊(cè)彻犁。

最后我們建立一個(gè)測(cè)試類WeatherStation來進(jìn)行測(cè)試

public class WeatherStation {
    public static void main(String[] args){
        WeatherData1 weatherData=new WeatherData1();
        
        CurrentConditionsDisplay currentConditionsDisplay=new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay=new StatisticsDisplay(weatherData);
        ForecastDisplay forecastDisplay=new ForecastDisplay(weatherData);
        
        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);
        weatherData.setMeasurements(78, 90, 29.2f);
    }
}

最終結(jié)果如下


測(cè)試結(jié)果

到這里我們已經(jīng)講解完觀察者模式的一種實(shí)現(xiàn)方式汞幢。但是這我們也提出一個(gè)問題,用來發(fā)散输钩。

是否能夠在主題中提供向外的可以讓觀察者自己獲取自己想要數(shù)據(jù),而并非將所有的數(shù)據(jù)都推送給觀察者姻氨?也就是在Push(推)的同時(shí)我們也可以pull(拉)剪验。

4.Java內(nèi)置的觀察者模式

剛才的問題功戚,其實(shí)熟悉Java語言的開發(fā)者會(huì)發(fā)現(xiàn),在Java中已經(jīng)有相應(yīng)的模式届宠,如果熟悉的可以直接跳過本章乘粒。
在java.util包下有Observer和Observable類灯萍,這兩個(gè)類的結(jié)構(gòu)跟我們遇到的Subject和Observer模型有些類似。甚至可是隨意使用push(推)或者pull(拉)
這里我們使用在線的Java API網(wǎng)站在線Java API文檔
首先查詢Observer的API

Observer API文檔

這個(gè)與我們所寫的Observer結(jié)構(gòu)幾乎相同属桦,只是在推送是把Observable類一起推送聂宾,這樣用戶既可以push也可以使用pull的方式诊笤。那么Observable的結(jié)構(gòu)呢

Observable API 介紹
Observable API 方法介紹

我們發(fā)現(xiàn)這里Observable是類與我們之前Subject作為接口的方式稍微有區(qū)別讨跟;并且Observable類其他方法更全。那么使用類的方式和使用接口的影響我們?cè)诤竺鏁?huì)繼續(xù)講茶袒。并且這里我們關(guān)注setChanged()方法告訴被觀察者的數(shù)據(jù)發(fā)生改變
那么凉馆,如果要使用Java中自帶的觀察者模式來修改原有氣象站業(yè)務(wù)會(huì)如何。

首先向叉,我們來分析更改后氣象站的模型:


Java內(nèi)置觀察者模式 氣象站

4.1Java內(nèi)置觀察者模式運(yùn)作模式

相對(duì)于于之前Subject和Observer的模式母谎,Java內(nèi)置自帶的觀察者模式運(yùn)行稍微有些差異奇唤。

  • 將對(duì)象變成觀察者只需要實(shí)現(xiàn)Observer(java.util.Observer)接口,然后調(diào)用任何Observable的addObserver()方法即可。如果要?jiǎng)h除觀察者来惧,調(diào)用deleteObserver()即可供搀。

  • 被觀察者若要推送通知,需要對(duì)象繼承Observable(java.util.Observable)類胎源,并先調(diào)用setChanged(),首先標(biāo)記狀態(tài)已經(jīng)改變屿脐。然后調(diào)用notifyObservers()方法中的一個(gè):notifyObservers()(通知觀察者pull數(shù)據(jù))或notifyObserers(Object object)(通知觀察者push數(shù)據(jù))

那么作為觀察者如何處理被觀察者推送出的數(shù)據(jù)呢的诵。
這里邏輯如下:

  • 觀察者(Observer)必須在update(Observable o,Object object).前一個(gè)參數(shù)用來讓觀察者知道是哪個(gè)被觀察者推送數(shù)據(jù)。后一個(gè)object為推送數(shù)據(jù)烦粒,允許為null代赁。

4.2 setChanged()

在Observable類中setChanged()方法一開始我也有疑惑芭碍,為何在推送之前需要調(diào)用該方法。后來查閱資料和Java API發(fā)現(xiàn)它很好的一個(gè)用處廉涕。我們先來查看java的源碼


Observable類中setChanged()方法

Observable類中notifyObservers()方法

這里必須標(biāo)記為true才會(huì)推送消息狐蜕,那么這個(gè)到底有何好處,我們拿氣象站模型來分析婆瓜。
如果沒有setChanged方法贡羔,也是之前的Subject和Observer模型里乖寒,一旦數(shù)據(jù)發(fā)生細(xì)微的變化,我們都會(huì)對(duì)所有的觀察者進(jìn)行推送磅轻。如果我們需要在溫度變化1攝氏度以上才發(fā)送推送逐虚,調(diào)用setChanged()方法更加有效。當(dāng)然撮躁,這個(gè)功能使用場(chǎng)景很少把曼,但是也不排除會(huì)用到漓穿。當(dāng)然更改Object和Observer模型也是可以做到這個(gè)效果的!P亡ā山害!

4.3 Java內(nèi)置觀察者更改氣象站

那么利用氣象站模型來實(shí)際操作一下浪慌,依照之前的模型我們代碼應(yīng)該如下
1.WeatherData2

public class WeatherData2 extends Observable{
    
    private float temperature;
    
    private float humidity;
    
    private float pressure;
    
    //構(gòu)造器不需要為了記住觀察者建立數(shù)據(jù)模型
    public WeatherData2(){
        
    }
    
    
    public void measurementsChanged(){
        //在調(diào)用notifyObserver()需要指示狀態(tài)已經(jīng)更改了
        setChanged();
       //這里未使用notifyObserver(object),所以數(shù)據(jù)采用拉的邏輯
        notifyObservers(this);
    }
    
    public void setMeasurements(float temperature,float humidity,float pressure){
        this.temperature=temperature;
        this.humidity=humidity;
        this.pressure=pressure;
        measurementsChanged();
    }
    
    //以下方法為pull操作提供
    public float getTemperature() {
        return temperature;
    }
    
    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}

2.CurrentConditionsDisplay1

public class CurrentConditionsDisplay1 implements Observer,DisplayElement{
    
    private Observable observable;
    
    private float temperature;
    
    private float humidity;
    
    private float pressure;
    
    //構(gòu)造器需要傳入Observable參數(shù)权纤,并登記成為觀察者
    public CurrentConditionsDisplay1(Observable observable){
        this.observable=observable;
        observable.addObserver(this);
    }
    
    //update方法增加Observable和數(shù)據(jù)對(duì)象作為參數(shù)
    public void update(Observable o, Object arg) {
        if(arg instanceof WeatherData2){
            WeatherData2 weatherData2=(WeatherData2) arg;
            this.temperature=weatherData2.getTemperature();
            this.humidity=weatherData2.getHumidity();
            this.pressure=weatherData2.getPressure();
            display();
        }
        
    }
    
    /**
     * 
     * display:展示布告板內(nèi)容
     * @author 吉祥
     * @since JDK 1.6
     */
    public void display(){
        System.out.println("Current conditons:"+temperature
                +"F degrees and "+humidity+"% humidity");
    }
}

3.ForecastDisplay1

public class ForecastDisplay1 implements Observer,DisplayElement{
    private float temperature;
    private float humidity;
    private float pressure;
    private Observable observable;
    
    public ForecastDisplay1(Observable observable){
        this.observable=observable;
        observable.addObserver(this);
    }
    
   
    public void update(Observable o,Object arg){
        if(arg instanceof WeatherData2){
            WeatherData2 weatherData2=(WeatherData2) arg;
            this.temperature=weatherData2.getTemperature();
            this.humidity=weatherData2.getHumidity();
            this.pressure=weatherData2.getPressure();
            display();
        }
    }
        

    /**
     * 
     * display:展示布告板內(nèi)容
     * @author 吉祥
     * @since JDK 1.6
     */
    public void display(){
        System.out.println("Forecast: More of the same");
    }

}

4.StatisticsDisplay1

public class StatisticsDisplay1 implements Observer,DisplayElement{

    private float temperature;
    private float humidity;
    private float pressure;
    private Observable observable;
    
    public StatisticsDisplay1(Observable observable){
        this.observable=observable;
        observable.addObserver(this);
    }
    
   
    public void update(Observable o,Object arg){
        if(arg instanceof WeatherData2){
            WeatherData2 weatherData2=(WeatherData2) arg;
            this.temperature=weatherData2.getTemperature();
            this.humidity=weatherData2.getHumidity();
            this.pressure=weatherData2.getPressure();
            display();
        }
    }
        

    /**
     * 
     * display:展示布告板內(nèi)容
     * @author 吉祥
     * @since JDK 1.6
     */
    public void display(){
        System.out.println("Avg/Max/Min temperature= "+temperature
                +"/"+temperature+"/"+temperature);
    }
}

最后進(jìn)行測(cè)試:
WeatherStation1

public class WeatherStation1 {
    public static void main(String[] args){
        WeatherData2 weatherData=new WeatherData2();
        
        CurrentConditionsDisplay1 currentConditionsDisplay=new CurrentConditionsDisplay1(weatherData);
        StatisticsDisplay1 statisticsDisplay=new StatisticsDisplay1(weatherData);
        ForecastDisplay1 forecastDisplay=new ForecastDisplay1(weatherData);
        
        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);
        weatherData.setMeasurements(78, 90, 29.2f);
       
    }
}

結(jié)果最終如下:


Java 內(nèi)置觀察者模式

我們對(duì)比之前Subject和Observer的觀察者模式會(huì)發(fā)現(xiàn)兩者輸出順序不一樣外邓,這是為什么损话?

其實(shí)java.util.Observable不依賴于觀察者被通知的順序的,并且實(shí)現(xiàn)了他的notifyObserver()方法光涂,這會(huì)導(dǎo)致通知觀察者的順序不同于Subject和Observer模型在具體類實(shí)現(xiàn)notifyObserver()方法拧烦。其實(shí)兩者都沒有任何的代碼誤差恋博,只是實(shí)現(xiàn)的方式不同導(dǎo)致不同的結(jié)果。

但是java.util.Observable類卻違背了之前第一章中針對(duì)接口編程重虑,而非針對(duì)實(shí)現(xiàn)編程秦士∮栏撸恐怖的是命爬,它也沒有接口實(shí)現(xiàn),這就導(dǎo)致它的使用具有很高的局限性和低復(fù)用性皆愉。如果一個(gè)對(duì)象不僅僅是被觀察者艇抠,同時(shí)還是另一個(gè)超類的子類的時(shí)候,我們無法使用多繼承的方式來實(shí)現(xiàn)异剥。我們?nèi)绻孕型卣沟脑捫踔兀銜?huì)發(fā)現(xiàn)setChanged()方法是protected方法,這就表示只有java.util.Observable自身和其子類才可以使用這個(gè)方法督怜。這就違反了第二個(gè)設(shè)計(jì)原則---------"多用組合亮蛔,少用繼承"。這也是我一般不會(huì)使用Java自帶的設(shè)計(jì)者模式的原因辣吃。

現(xiàn)在比較流行的觀察者模式芬探,也就是RxJava偷仿,但是由于這個(gè)框架涉及不僅僅有觀察這模式,在之后整個(gè)設(shè)計(jì)模式整理玩不后节榜,我會(huì)集中再講别智。

5.總結(jié)

到此,觀察者模式的講解已經(jīng)全部講解完成讳窟±龇龋總結(jié)一下硬猫。

第四設(shè)計(jì)原則
為交互對(duì)象之間的松耦合涉及而努力

觀察者模式
在對(duì)象之間定義一對(duì)多的依賴,這樣一來坑雅,當(dāng)一個(gè)對(duì)象改變狀態(tài)盔性,依賴它的對(duì)象都會(huì)收到通知冕香,并自動(dòng)更新后豫。

相應(yīng)的資料和代碼托管地址https://github.com/jixiang52002/HFDesignPattern

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末挫酿,一起剝皮案震驚了整個(gè)濱河市愕难,隨后出現(xiàn)的幾起案子猫缭,更是在濱河造成了極大的恐慌,老刑警劉巖芝加,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藏杖,死亡現(xiàn)場(chǎng)離奇詭異脉顿,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)来吩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門误褪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碾褂,“玉大人历葛,你說我怎么就攤上這事恤溶。” “怎么了鸠天?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵稠集,是天一觀的道長。 經(jīng)常有香客問我痹籍,道長晦鞋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任线定,我火速辦了婚禮渔肩,結(jié)果婚禮上拇惋,老公的妹妹穿的比我還像新娘撑帖。我一直安慰自己,他們只是感情好蛉艾,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布勿侯。 她就那樣靜靜地躺著缴罗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪兵钮。 梳的紋絲不亂的頭發(fā)上舌界,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天呻拌,我揣著相機(jī)與錄音,去河邊找鬼靴拱。 笑死缭嫡,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的耕突。 我是一名探鬼主播评架,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼纵诞,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼室梅!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纸俭,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤南窗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后窒悔,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體敌买,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡放妈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年芜抒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了托启。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡拐迁,死狀恐怖线召,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哈打,我是刑警寧澤讯壶,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布伏蚊,位于F島的核電站,受9級(jí)特大地震影響氛改,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜比伏,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一平窘、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧凳怨,春花似錦瑰艘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽李剖。三九已至芒率,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間篙顺,已是汗流浹背偶芍。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留德玫,地道東北人匪蟀。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像宰僧,于是被迫代替她去往敵國和親材彪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

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