HeadFirst設計模式(二) - 觀察者模式

有趣的事情發生時,可千萬別錯過了!java

    觀察者模式是JDK中使用最多的模式之一,很是有用。咱們也會一併介紹一對多關係,以及鬆耦合。有個觀察者,你將會消息靈通。編程

工做合約設計模式

    某軟件公司接到一個工做合約,內容以下:函數


    恭喜貴公司獲選爲敝公司(Weather-O-Rama氣象站)創建下一代Internate氣象站!測試

    該氣象站必須創建在咱們專利申請中的WeatherData對象上,由WeatherData負責監測目前的天氣狀況(溫度、溼度和睦壓等)。咱們但願貴公司創建一個應用,有三種佈告板,分別顯示目前的狀況,氣象統計及簡單的預報。當WeatherData對象得到最新的監測數據時,三種佈告板實時更新。this

    並且,這是一個能夠拓展的氣象站,Weather-O-Rama氣象站但願發佈一組API,好讓其餘開發人員能夠寫出本身的氣象佈告板,並插入次應用中。咱們但願貴公司提供這樣的API。spa


氣象監測站應用的概況.net

    此係統中的三個部分是氣象站(獲取實際氣象數據的物理裝置)、WeatherData對象(最終來自氣象站的數據)和佈告板(顯示目前天氣情況給用戶看)。如圖:設計


    WeatherData對象知道如何跟物理氣象站聯繫,以得到監控的實施氣象數據。WeatherData對象會隨即更新三個佈告板的顯示:目前情況(溫度、溼度、氣壓)、氣象統計和天氣預報。code

    咱們的工做就是創建一個應用,利用WeatherData對象得到數據,並更新三個佈告板。    

    瞧一瞧客戶給提供的WeatherData類吧。

    咱們的工做是實現measurementsChanged()方法,好讓它更新目前的情況、氣象統計和天氣預報的顯示佈告板。

咱們目前知道什麼?

    Weather-O-Rama氣象站的要求說明並非很清楚,咱們必須搞懂該作些什麼,那麼,咱們目前知道些什麼呢?

  • WeatherData類具備getter方法,能夠取得三個測量值:溫度、溼度與氣壓。

  • 當新的測量數據準備好時,measurementsChanged()方法就會被調用(咱們不在意次方法是如何被調用的,咱們只在意它被調用了,也許被某個其餘的程序調用的,好比硬件採集器,這是Weather-O-Rama氣象站提供的WeatherData類已經實現的功能)。

  • 咱們須要實現三個使用天氣數據的佈告板,一旦WeatherData有新的測量數據就立刻更新。

  • 次系統必須可拓展,讓其餘開發人員自定義佈告板。

先看一個錯誤的示範

    若是不進行思考,直接進入開發階段,只爲了完成工做而工做,那麼這是第一個可能的實現。咱們依照Weather-O-Rama氣象站開發人員的暗示,在客戶提供的類的measurementsChanged()方法中添加咱們的代碼:

package cn.net.bysoft.observer;

/**
 * 這是一個沒有通過任何思考而寫的代碼。
 * */
public class WeatherData {

    // 該方法在硬件有最新監測數據時被調用。
    public void measurementsChanged() {
        //    該類中的代碼又咱們公司實現。
        
        // 調用三個getter方法得到最新的監測數據。
        float temp = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();

        // 更新三個咱們公司本身開發的佈告板類。
        currentConditionsDispaly.update(temp, humidity, pressure);
        statisticsDisplay.update(temp, humidity, pressure);
        forecastDispaly.update(temp, humidity, pressure);
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }

    // 這三個setter方法可能會在硬件監測到最新的氣象數據時被賦值。
    // 而後硬件會調用measurementsChanged()方法。
    public void setTemperature(float temperature) {
        this.temperature = temperature;
    }

    public void setHumidity(float humidity) {
        this.humidity = humidity;
    }

    public void setPressure(float pressure) {
        this.pressure = pressure;
    }

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

    在咱們的第一個實現中,存在一下的缺點:

  • 咱們是針對具體實現編程,而非針對接口;

  • 對於每一個新的佈告版,咱們都得修改代碼;

  • 咱們沒法在運行時動態地添加或刪除佈告板;

  • 咱們還沒有封裝改變的部分;

咱們的實現有什麼不對?

    針對具體實現編程,會致使咱們之後在添加或刪除佈告板時必須修改程序。並且,佈告板的update()方法看起來是一個統一的接口,佈告板的方法名稱都是update(),參數都是溫度、溼度和睦壓。

    如今就來看看觀察者模式,而後再回來看看如何將此模式應用到氣象觀測站。

    觀察者就好像訂閱報紙或者雜誌。

  1. 報社的業務就是出版報紙。

  2. 向某家報社訂閱報紙,只要他們有新報紙出版,就會給你送來。只要你是他們的訂戶,你就會一直收到新報紙。

  3. 當你不想再看報紙的時候,取消訂閱,他們就不會再送新報紙來。

  4. 只要報社還在運營,就會一直有人向他們訂閱報紙或取消訂閱。

    訂閱者+出版者=觀察者模式,只是名稱不太同樣。

    出版者改成主題(Subject),訂閱者改成觀察者(Observer),一個主題對應多個觀察者,是一個一對多的關係。來看一下觀察者的類圖:

    這裏有一個設計原則:

設計原則:

爲了交互對象之間的鬆耦合設計而努力。

鬆耦合的威力

    當兩個對象之間鬆耦合,他們依然能夠交互,可是不太清楚彼此的細節。

    觀察者模式提供了一種對象設計,讓主題和觀察者之間鬆耦合。

    關於觀察者的一切,主題值知道觀察者實現了某個接口。主題不須要知道觀察者的具體類是誰,作了些什麼或其餘任何細節。

    任什麼時候候咱們均可以添加新的觀察者。由於主題惟一依賴的東西是一個實現Observer接口的對象列表,因此咱們能夠隨時添加觀察者。事實上,在運行時咱們能夠用新的觀察者取代現有的觀察者,主題不會受到任何影響。一樣的,也能夠在任什麼時候候刪除某些觀察者。

    有新類型出現的觀察者出現時,主題的代碼不須要修改。咱們能夠獨立地複用主題或觀察者。若是咱們在其餘任何地方須要使用主題或觀察者,能夠輕易地複用,由於兩者並不是緊耦合,而是鬆耦合。

    鬆耦合的設計之因此能讓咱們創建有彈性的OO系統,可以應對變化,是由於對象之間的互相依賴降到了最低。

設計與實現氣象站

    瞭解了這麼多,開始設計與實現氣象站吧。

    設計類圖如上圖所示,讓咱們從創建接口開始吧!

package cn.net.bysoft.observer;

/**
 * 佈告板顯示信息接口。
 * */
public interface DisplayElement {
    /**
     * 當佈告板須要顯示時,調用次方法。
     * */
    public void display();
}
package cn.net.bysoft.observer;

/**
 * 觀察者接口。
 * */
public interface Observer {
    /**
     * 當氣象監測數據改變時,主題會把這些狀態值看成方法的參數,傳送給觀察者。
     * */
    public void update(float temp, float humidity, float perssure);
}
package cn.net.bysoft.observer;

/**
 * 主題接口。
 * */
public interface Subject {
    /**
     * registerObserver和removeObserver方法都須要一個觀察者做爲變量,該觀察者是用來註冊或被刪除的。
     * */
    public void registerObserver(Observer ob);
    
    public void removeObserver(Observer ob);
    
    /**
     * 當主題的狀態改變時,這個方法會被調用,已通知全部的觀察者。
     * */
    public void notifyObservers();
}

    接下來,咱們要用觀察者模式實現WeatherData類了。

package cn.net.bysoft.observer;

import java.util.ArrayList;

public class WeatherData implements Subject {
    public WeatherData() {
        observers = new ArrayList<Observer>();
    }
    
    /**
     * 註冊觀察者。
     * */
    public void registerObserver(Observer ob) {
        observers.add(ob);
    }

    /**
     * 移除觀察者。
     * */
    public void removeObserver(Observer ob) {
        int index = observers.indexOf(ob);
        if (index >= 0)
            observers.remove(index);
    }

    /**
     * 當有最新的氣象監測數據時被調用。↓下面的函數調用。
     * */
    public void notifyObservers() {
        for (Observer ob : observers) {
            ob.update(this.temperature, this.himidity, this.pressure);
        }
    }
    
    /**
     * 氣象站硬件監測到新數據會調用該方法。↓下面的函數調用。
     * */
    public void measurementsChanged() {
        notifyObservers();
    }
    
    /**
     * 由於沒有氣象站,因此模擬一個硬件,得到氣象數據調用measurementsChanged()方法。
     * 在main()函數中使用。
     * */
    public void setMeasurements(float temperature, float himidity, float pressure) {
        this.temperature = temperature;
        this.himidity = himidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    public ArrayList<Observer> getObservers() {
        return observers;
    }

    public void setObservers(ArrayList<Observer> observers) {
        this.observers = observers;
    }

    public float getTemperature() {
        return temperature;
    }

    public void setTemperature(float temperature) {
        this.temperature = temperature;
    }

    public float getHimidity() {
        return himidity;
    }

    public void setHimidity(float himidity) {
        this.himidity = himidity;
    }

    public float getPressure() {
        return pressure;
    }

    public void setPressure(float pressure) {
        this.pressure = pressure;
    }

    private ArrayList<Observer> observers;
    private float temperature;
    private float himidity;
    private float pressure;
}

最後,編寫佈告板吧!

    咱們已經把WeatherData類寫出來了,如今輪到佈告板了。Weather-O-Rama氣象站訂購了三個佈告板,首先來實現其中一個佈告板。

package cn.net.bysoft.observer;

public class CurrentConditionsDisplay implements Observer, DisplayElement {

    public CurrentConditionsDisplay() {}
    public CurrentConditionsDisplay(Subject weatherData) {
        //    實例化佈告板的時候,傳入一個主題,將本身註冊到主題中。
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    
    public void display() {
        System.out.println("當前conditions:" + temperature + "F degrees and "
                + himidity + "% humidity");
    }

    public void update(float temp, float humidity, float perssure) {
        this.temperature = temp;
        this.himidity = humidity;
        // 更新數據後刷新佈告板。
        display();
    }

    private float temperature;
    private float himidity;
    //    要觀察的主題。
    private Subject weatherData;
}

    佈告板已經實現完畢,編寫一個main()方法測試一下觀察者模式吧!

package cn.net.bysoft.observer;

public class Main {

    public static void main(String[] args) {
        // 首先,建立一個主題。
        // 這裏應該使用Subject weatherData = new WeatherData();來建立主題。
        // 可是咱們在WeatherData中添加了一個模擬硬件發送氣象數據的函數,Subject中並無這個函數。
        // 因此使用了WeatherData來建立主題。
        WeatherData weatherData = new WeatherData();
        // 創建一個觀察者(也就是佈告板),把主題傳給觀察者,在觀察者的構造函數中訂閱主題。
        Observer ccDisplay = new CurrentConditionsDisplay(weatherData);

        // 模擬一個氣象變化的狀況。
        weatherData.setMeasurements(80, 65, 30.4f);
        System.out.println("\n====================\n");

        // 又變化了!
        weatherData.setMeasurements(80, 65, 30.4f);

        /**
         * output: 
         * 當前conditions:80.0F degrees and 65.0% humidity
         * 
         * ====================
         * 
         * 當前conditions:80.0F degrees and 65.0% humidity
         * 
         * */
    }

}

    另外兩個佈告板如上代碼,另外,Java中提供了內置的觀察者模式接口,它們是:

  • java.util.Observable

  • java.util.Observer

    想要進一步瞭解能夠網上查找資料,或者和我同樣購買HeadFirst設計模式這本書,這本書真的很不錯。

    以上就是觀察者模式。

相關文章
相關標籤/搜索