設計模式:第二篇--觀察者模式

  • 定義:觀察者模式定義了一系列一對多的關系,當一個對象改變狀態(tài)韩脑,其他依賴者都會收到通知直撤。



    從類圖上看其實就是一個Subject(主題)依賴多個Observer(觀察者)哎榴,一個Observer依賴一個Subject寝杖。一對多的關系违施。Subject定義了觀察者注冊和注銷的方。以及主題改變時通知在線觀察者的方法朝墩。Observer定義了當主題改變式醉拓,被通知時要調用的update方法。關鍵在這個依賴收苏。當Observer被實例化的時候亿卤,會告訴調用Subject的registerObserver()注冊到Subject。當Subject更新的時候會最終調用Observer的update方法通知到觀察者鹿霸。

  • 松耦合設計原則:連個對象之間要交互排吴,暴露的東西越少越好。只需要知道對方能干嘛懦鼠,而不需要知道具體怎么實現(xiàn)的钻哩。本例中。Observer不需要知道Subject怎么通知我的肛冶,只需要知道街氢,通知我update方法被調用了即可。反過來睦袖。Subject不需要知道Observer具體是誰珊肃,做了什么。只需要知道它是個Observer即可馅笙。

案例:氣象站的設計

需求: 目前已經(jīng)有WeatherData類伦乔。里面有獲取溫度、濕度董习、氣壓的方法烈和。當新的數(shù)據(jù)備妥時measurementsChanged方法會被調用。現(xiàn)在需要增加三個使用天氣數(shù)據(jù)的的布告板皿淋,分別是“目前狀況”布告招刹、“天氣預報”布告、“氣象統(tǒng)計”布告窝趣。并且要求這些布告是可以拓展的疯暑,也就是可以刪除也可以增加其他布告板。

錯誤的代碼設計:面向過程(直接懟代碼)

既然知道了當數(shù)據(jù)更新的時候measurementsChanged方法會被調用高帖,那直接在上面寫代碼就可以實現(xiàn)需求了。于是寫出了類似下面的代碼畦粮。簡稱“直接懟”散址。這就很不友好了乖阵。這樣的后果就是日后如果氣象站有所變更,我需要直接修改代碼预麸。在公共的代碼區(qū)域直接修改代碼會造成意想不到的結果瞪浸。比如所有被通知的布告板,出現(xiàn)了不該出現(xiàn)的數(shù)據(jù)吏祸。

public class WeatherData {


    public String getTemperature(){

        return "溫度";
    }

    public String getHumidity(){
        return "濕度";
    }

    public String getPressure(){
        return "氣壓";
    }

    /**
     * 新的測量數(shù)據(jù)備好后會被調用
     */
    public void measurementssChanged(){


        String temperature = getTemperature();
        String humidity = getHumidity();
        String pressure = getPressure();

        //目前
        currentConditionsDisplay.update(temperature,humidity,pressure);
        //統(tǒng)計
        statisticsDisplay.update(temperature, humidity, pressure);
        //預報
        forecastDisplay.update(temperature, humidity, pressure);


    }

}

使用觀察者模式

思考:該需求很適合使用觀察者模式对蒲,氣象站需要給布告板數(shù)據(jù),而布告板展示數(shù)據(jù)贡翘。這不就是觀察者模式的經(jīng)典嗎蹈矮。

1、定義被觀察者接口:主題

/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 9:10.
 *
 * 主題接口:對象使用此接口注冊為觀察者鸣驱》耗瘢或者把自己是刪除。
 */
public interface Subject {

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

    /**
     * 從觀察者列表里刪除(注銷)
     */
    void removeObserver(Observer observer);

    /**
     * 通知當前所有觀察者踊东,主題狀態(tài)改變
     */
    void notifyObserver();

}

2北滥、定義觀察者接口

/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 9:14.
 *
 * 觀察者接口:所有潛在的觀察者必須實現(xiàn)觀察者接口,該demo中只有update一個方法闸翅,當Subject狀態(tài)改變時,它被調用
 */
public interface Observer {

    void udpate(float temp, float humidity,float pressure);

}

3、定義業(yè)務功能接口

/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 9:31.
 */
public interface DisplayElement {

    /**
     * 布告板展示時調用此方法
     */
    void display();

}

4流部、實現(xiàn)具體主題

/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/14 16:48.
 */
public class WeatherData implements Subject{

    //依賴觀察者:subject包含多個Observer
    private ArrayList<Observer> observers;
    
    //溫度    
    private float temperature;
    //濕度
    private float humidity;
    //氣壓
    private float pressure;


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


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

    @Override
    public void removeObserver(Observer observer) {
        if (observers.indexOf(observer) >= 0) {
            observers.remove(observer);
        }
    }

    @Override
    public void notifyObserver() {
        for (Observer observer : observers) {
            observer.udpate(temperature,humidity,pressure);
        }
    }

    /**
     * 新的測量數(shù)據(jù)備好后會被調用
     */
    public void measurementsChanged(){

        notifyObserver();

    }


    public void setMeasurements(float temperature,float humidity, float pressure){
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }


}

5导狡、實現(xiàn)具體觀察者

/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 9:32.
 */
public class CurrentConditionDisplay implements Observer, DisplayElement {
    //依賴具體被觀察者(主題)
    private Subject weatherData;

    private float temperature;
    private float humidity;
    private float pressure;
    
    public CurrentConditionDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        //注冊
        weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("CurrentCondition:");
        System.out.println("temperature = " + temperature);
        System.out.println("humidity = " + humidity);
        System.out.println("pressure = " + pressure);

    }

    @Override
    public void udpate(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }
}

/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 9:32.
 */
public class ForecastDisplay implements Observer, DisplayElement {

    private Subject weatherData;

    private float temperature;
    private float humidity;
    private float pressure;

    public ForecastDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        //注冊
        weatherData.registerObserver(this);
    }


    @Override
    public void display() {
        System.out.println("Forecast:");
        System.out.println("temperature = " + temperature);
        System.out.println("humidity = " + humidity);
        System.out.println("pressure = " + pressure);

    }

    @Override
    public void udpate(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }
}
/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 9:32.
 */
public class StatisticDisplay implements Observer, DisplayElement {

    private Subject weatherData;

    private float temperature;
    private float humidity;
    private float pressure;

    public StatisticDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        //注冊
        weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("Statistic:");
        System.out.println("temperature = " + temperature);
        System.out.println("humidity = " + humidity);
        System.out.println("pressure = " + pressure);

    }

    @Override
    public void udpate(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }
}

6、測試

public class Test {

    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();

        CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
        StatisticDisplay statisticDisplay = new StatisticDisplay(weatherData);
        ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

        weatherData.setMeasurements(12,12,12);
        weatherData.setMeasurements(13,13,13);

    }
}

總結

思考:
觀察者模式的核心思想小編理解是松耦合的實現(xiàn)思想遗菠。相互依賴联喘,僅僅暴露需要傳輸?shù)牟糠郑渌[藏在各自的區(qū)域中辙纬。相互只知道相互需要知道的東西豁遭,一點不多一點不少。利用組合的思想贺拣。面向接口編程蓖谢。方便拓展并且實現(xiàn)了解耦,主題和觀察者解耦譬涡,業(yè)務和觀察者解耦闪幽。代碼是相互獨立的,使用是相互依賴的涡匀。

缺點:
本例后盯腌,當我們嘗試去拓展一個新的Subject的時候,我們會發(fā)現(xiàn)我們寫了重復的代碼陨瘩。注冊刪除和通知Observer這些方法在每一個實現(xiàn)類當中都需要去實現(xiàn)腕够。關于這個级乍,使用jdk8的小伙伴們說很容易解決的啦。jdk8支持接口方法的默認實現(xiàn)嘛帚湘,子類可以不實現(xiàn)這些方法玫荣。 比如這樣。

public interface Subject {

    ArrayList<Observer> observers = new ArrayList<Observer>();

    /**
     * 注冊為觀察者
     */
    default void registerObserver(Observer observer){
        observers.add(observer);
    }

    /**
     * 從觀察者列表里刪除(注銷)
     */
    default void removeObserver(Observer observer){
        if (observers.indexOf(observer) >= 0) {
            observers.remove(observer);
        }
    }

    /**
     * 通知當前所有觀察者大诸,主題狀態(tài)改變
     */
    void notifyObserver();

}


public class WeatherData implements Subject {

    private float temperature;
    private float humidity;
    private float pressure;


    @Override
    public void notifyObserver() {
        for (Observer observer : observers) {
            observer.udpate(temperature, humidity, pressure);
        }
    }

    /**
     * 新的測量數(shù)據(jù)備好后會被調用
     */
    public void measurementsChanged() {

        notifyObserver();

    }

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

JDK內置觀察者模式

其實在java.util下已經(jīng)內置好咯額觀察者模式供我們使用捅厂。只不過不同于上面的案例。我們可以看一下具體區(qū)別在哪资柔。

JDK定義Observable類為被觀察者焙贷,對,沒看錯建邓,這個是類盈厘,比起之前我們自己定義的Subject,多了一個狀態(tài)changed屬性官边,調用setChanged方法設置為true沸手,調用notifyObservers方法的時候設置為false,changed的設計可以讓我們更有效的控制變化的通知頻率注簿,更加靈活契吉。觀察者是接口Observer。update方法接受一個具體被觀察者以及Object類型的參數(shù)诡渴。接受的參數(shù)就包含了依賴的被觀察者捐晶,第二個參數(shù)方便我們根據(jù)業(yè)務傳入其他的參數(shù)。

使用JDK內置觀察者模式改造氣象站

import java.util.Observable;

public class WeatherData extends Observable {


    private float temperature;
    private float humidity;
    private float pressure;


    public WeatherData() {
    }


    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }


    /**
     * 新的測量數(shù)據(jù)備好后會被調用
     */
    public void measurementsChanged() {
        setChanged();
        notifyObservers();

    }

    public float getPressure() {
        return pressure;
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }
}


import com.example.observer.demo5.DisplayElement;
import com.example.observer.demo5.subject.WeatherData;

import java.util.Observable;
import java.util.Observer;

/**
 * Project <demo-project>
 * Created by jorgezhong on 2018/9/18 9:32.
 */
public class CurrentConditionDisplay implements Observer, DisplayElement {

    private Observable observable;

    private float temperature;
    private float humidity;
    private float pressure;

    public CurrentConditionDisplay(Observable observable) {
        this.observable = observable;
        //注冊
        observable.addObserver(this);
    }

    @Override
    public void display() {
        System.out.println("CurrentCondition:");
        System.out.println("temperature = " + temperature);
        System.out.println("humidity = " + humidity);
        System.out.println("pressure = " + pressure);

    }

    @Override
    public void update(Observable observable, Object arg) {
        if (observable instanceof WeatherData) {
            WeatherData weatherData = (WeatherData) observable;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            this.pressure = weatherData.getPressure();
            display();
        }

    }
}
image.png

案例總結:

JDK內置的觀察者模式考慮的會多一點妄辩,比如changed的設置惑灵。比如Observer接收Observable和參數(shù)Object會更加適合實際應用。但是由于被觀察者使用的是繼承眼耀,我們的設計原則是多用組合少用繼承英支。既然可以用接口改造那就用接口改造好了。jdk8也支持接口方法的默認實現(xiàn)哮伟。還有一點區(qū)別干花,用jdk的實現(xiàn)是無法保證通知順序的,因此不要選擇依賴通知的順序楞黄。

設計原則:

  • 松耦合:所有的設計都盡量松耦合池凄。設計為此而努力
  • 開閉原則:對拓展開放,對修改關閉鬼廓。觀察者模式中我們可以通過拓展被觀察者而實現(xiàn)拓展肿仑,不對舊的代碼做出修改符合開閉原則。

-重點設計:我們發(fā)現(xiàn)使用了設計模式會大量使用接口抽象方法。抽象多了尤慰,層次就會增多勾邦。同樣的,遵循開閉原則的代碼一般都會引入新的抽象層次割择,增加代碼的復雜性,太復雜的代碼往往都不好理解萎河,失去了其意義荔泳。因此對重點進行設計,避免過度設計虐杯。

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末玛歌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子擎椰,更是在濱河造成了極大的恐慌支子,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,914評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件达舒,死亡現(xiàn)場離奇詭異值朋,居然都是意外死亡,警方通過查閱死者的電腦和手機巩搏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評論 2 383
  • 文/潘曉璐 我一進店門昨登,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人贯底,你說我怎么就攤上這事丰辣。” “怎么了禽捆?”我有些...
    開封第一講書人閱讀 156,531評論 0 345
  • 文/不壞的土叔 我叫張陵笙什,是天一觀的道長。 經(jīng)常有香客問我胚想,道長琐凭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,309評論 1 282
  • 正文 為了忘掉前任顿仇,我火速辦了婚禮淘正,結果婚禮上,老公的妹妹穿的比我還像新娘臼闻。我一直安慰自己鸿吆,他們只是感情好,可當我...
    茶點故事閱讀 65,381評論 5 384
  • 文/花漫 我一把揭開白布述呐。 她就那樣靜靜地躺著惩淳,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上思犁,一...
    開封第一講書人閱讀 49,730評論 1 289
  • 那天代虾,我揣著相機與錄音,去河邊找鬼激蹲。 笑死棉磨,一個胖子當著我的面吹牛,可吹牛的內容都是我干的学辱。 我是一名探鬼主播乘瓤,決...
    沈念sama閱讀 38,882評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼策泣!你這毒婦竟也來了衙傀?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,643評論 0 266
  • 序言:老撾萬榮一對情侶失蹤萨咕,失蹤者是張志新(化名)和其女友劉穎统抬,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體危队,經(jīng)...
    沈念sama閱讀 44,095評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡聪建,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,448評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了茫陆。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妆偏。...
    茶點故事閱讀 38,566評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖盅弛,靈堂內的尸體忽然破棺而出钱骂,到底是詐尸還是另有隱情,我是刑警寧澤挪鹏,帶...
    沈念sama閱讀 34,253評論 4 328
  • 正文 年R本政府宣布见秽,位于F島的核電站,受9級特大地震影響讨盒,放射性物質發(fā)生泄漏解取。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,829評論 3 312
  • 文/蒙蒙 一返顺、第九天 我趴在偏房一處隱蔽的房頂上張望禀苦。 院中可真熱鬧,春花似錦遂鹊、人聲如沸振乏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽慧邮。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間误澳,已是汗流浹背耻矮。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留忆谓,地道東北人裆装。 一個月前我還...
    沈念sama閱讀 46,248評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像倡缠,于是被迫代替她去往敵國和親米母。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,440評論 2 348

推薦閱讀更多精彩內容