觀察者模式

GitHub代碼

場景描述:我們得到了一個類WeatherData,它可以從氣象站取得數據,現在需要我們實現measurementsChanged()方法,將數據更新到相應的佈告板上。

初次嘗試

public void measurementsChanged(){
    float temp = getTemperature();
    float humidity = getHumidity();
    float pressure = getPressure();
    
    currentConditionsDisplay.update(temp, humidity, pressure);
    statisticsDisplay.update(temp, humidity, pressure);
    forecastDisplay.update(temp, humidity, pressure);
}

問題所在:首先這種實現是針對具體實現編程,如果我們新增或刪除佈告板時,需要修改對應程序代碼,這違反了我們之前提到的設計原則:針對接口編程。
其次update()方法參數相同,看起來可以提煉出一個接口。

我們嘗試使用觀察者模式來解決這些問題。

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

觀察者模式定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知並自動更新。

在這裏插入圖片描述
當兩個對象之間鬆耦合,它們依然可以交互,但是不太清楚彼此的細節。
觀察者模式提供了一種對象設計,讓主題和觀察者之間鬆耦合。

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

鬆耦合的設計之所以能讓我們建立有彈性的OO系統,能夠應對變化,是因爲對象之間的相互依賴降到了最低。

根據類圖,我們首先創建相應的接口

public interface Subject {
    public void registerObserver(Observer o);
    public void removeObserver(Observer o);
    public void notifyObservers();
}
public interface Observer {
    public void update(float temp, float humidity, float pressure);
}
public interface DisplayElement {
    public void display();
}

然後是具體的主題實現,它負責註冊、移除和通知觀察者。

/**
 * @Auther: yubt
 * @Description: push(推送數據到觀察者)
 * @Date: Created in 15:48 2019/2/21
 * @Modified By:
 */
public class WeatherData implements Subject {
    private ArrayList observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData(){
        observers = new ArrayList();
    }

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

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

    @Override
    public void notifyObservers() {
        for (int i = 0; i < observers.size(); i++){
            Observer observer = (Observer) observers.get(i);
            observer.update(temperature, humidity, pressure);
        }
    }

    public void measurementsChanged(){
        notifyObservers();
    }

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

接着是觀察者的實現

public class CurrentConditionsDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private Subject weatherData;

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

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

    @Override
    public void display() {
        System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
    }
}

然後我們測試一下

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

        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);
        weatherData.setMeasurements(78, 90, 29.2f);
    }
}

運行結果
在這裏插入圖片描述
可以看到當主題類調用相關方法時,會將相應的數據推送給觀察者,並由觀察者展示出來。

Java是有內置的觀察者模式的。分別是java.util.Observable和java.util.Observer。
同時,觀察者模式存在不同的實現形式,比如上面我們的實現是通過主題向觀察者進行消息的推送(push);我們還可以通過觀察者從主題拉取(pull)消息來實現觀察者模式。

我們看下相關的代碼實現。

/**
 * @Auther: yubt
 * @Description: pull(由觀察者拉取數據)
 * @Date: Created in 16:01 2019/2/21
 * @Modified By:
 */
public class WeatherData extends Observable {
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData(){}

    public void measurementsChanged(){
        setChanged(); 
        notifyObservers();
    }

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

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}
public class CurrentConditionsDisplay implements Observer, DisplayElement {
    Observable observable;
    private float temperature;
    private float humidity;

    public CurrentConditionsDisplay(Observable observable){
        this.observable = observable;
        observable.addObserver(this);
    }

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

    @Override
    public void display() {
        System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
    }
}

我們看到在WeatherData類的measurementsChanged()方法中調用了setChanged()方法,這個方法的具體作用如下。
在這裏插入圖片描述
不過,Java內置的觀察者模式有它的侷限。首先它是一個類,所以你必須設計一個類來繼承它。如果某類想同時具有Observable類和另一個超類的行爲,是無法實現的,因爲Java不支持多重繼承。這限制了Observable的複用潛力。
而且Observable中的setChanged()方法被保護起來了(被定義爲protected)。這意味着:除非你繼承自Observable,否則你無法創建Observable實例並組合到你自己的對象中來。

總結:觀察者模式的使用場景是非常明顯的,當存在這種通知的場景時,就可以使用觀察者模式。其實現起來也並不複雜,我們只需要一個主題接口,一個觀察者接口;在主題接口中,我們需要註冊、刪除及通知觀察者的方法,而通知方法其實就是通過主題中的觀察者隊列調用觀察者接口中的相應方法;而觀察者接口中的方法就是根據需求的具體實現了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章