觀察者模式


基本介紹

觀察者模式(Observer Design Pattern)也被稱為發布訂閱模式(Publish-Subscribe Design Pattern)

意圖:當一個對象的狀態發生改變時,所有依賴於它的對象都會得到通知並被自動更新。

觀察者模式屬於行為型模式, 大多應用於一些事件驅動模型(Spring涉及)或者游戲開發領域。

 

假設有一家氣象局,姑且就叫神盾氣象局吧,該氣象局委托我們構建一套系統,這個系統有兩個公告牌,需要我們顯示當前的實時天氣和未來的天氣預報。 當神盾氣象局發布新的天氣數據(WeatherData)后,兩個公告牌上顯示的天氣數據務必實時更新。神盾氣象局同時要求我們保證程序擁有足夠的可擴展性,因為以后隨時要新增其他的公告牌(如緊急公告等)。

他們最初始的設計如下:

public class WeatherData {

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

 

然鵝每當我需要刪除或者新增公告牌時,我就必須得修改 核心邏輯代碼,如此看來擴展性極差,違背了開閉原則(對擴展開放,對修改關閉)

從上述方案,我們已經大致了解該方案有很多問題

  • 擴展性較差
  • 代碼功能耦合嚴重
  • 核心功能代碼改來改去容易出問題

 

觀察者模式

觀察者模式通常情況所解決的需求場景:A對象(觀察者)被 B對象(被觀察者)的某種變化高度敏感,需要在B變化的那一刻A及時得到反饋。

舉個例子:小明過馬路,小明需要在紅綠燈由紅燈變成綠燈后到達馬路另一側,這個場景中,小明是觀察者,紅綠燈是被觀察者,當紅綠燈發生顏色改變時,小明需要得到反饋。

但是程序中的觀察和現實中所說的【觀察】有些許差異:

  • 觀察者不需要時刻盯着被觀察者(小明不需要每一秒盯着紅綠燈)
  • 采用注冊或者訂閱(Subscribe)的方式告訴被觀察者(紅綠燈變色后會廣播,小明可以聽到)

采取這樣被動的觀察方式,省去了反復檢索狀態的資源消耗,也能得到最快的反饋速度,其實就是由拉變成了推

 

觀察者模式通常基於 Subject(主題)和 Observer(觀察者)而設計,類圖如下

 

既然我們說了神盾氣象局最初的設計 多么的糟糕,那么我們現在就用觀察者模式來重構它吧!

首先我們基於上述的通用類圖,重新構建神盾氣象局的結構類圖以及編碼實現

 

主題接口

/**
 * 主題
 */
public interface Subject {

    void registerObserver(Observer observer);

    void removeObserver(Observer observer);

    void notifyObserver();

}

 

觀察者接口

/**
 * 觀察者
 */
public interface Observer {

    //反向更新
    void update(WeatherDetail weatherDetail);

}

 

公告牌接口

public interface DisplayElement {

    void display();

}

 

氣象數據Entity

@Data
@AllArgsConstructor
public class WeatherDetail {

    private double temperature; //當前溫度
    private double humidity; //當前濕度
    private double pressure; //當前氣壓
    private List<WeatherDetail> forecastDetails;//未來幾天的氣象數據詳情

}

 

氣象數據(被觀察者)- 核心

@Data
public class WeatherData implements Subject {

    private List<Observer> observers;
    private WeatherDetail weatherDetail;

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

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

    @Override
    public void removeObserver(Observer observer) {
        if (!observers.isEmpty()) {
            observers.remove(observer);
        }
    }

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

    public void setMeasurements(WeatherDetail weatherDetail){
        this.weatherDetail = weatherDetail;
        notifyObserver();
    }

}

 

顯示當前天氣的公告牌(CurrentConditionDisplay)

/**
 * 當前展板
 */
public class CurrentConditionDisplay implements Observer, DisplayElement {

    private double temperature; //當前溫度
    private double humidity; //當前濕度
    private double pressure; //當前氣壓

    public CurrentConditionDisplay(Subject weatherData) {
        //天氣數據
        weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("current-[temperature:" + temperature + ",humidity:" + humidity + ",pressure:" + pressure + "]");
    }

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

 

顯示未來幾天天氣的公告牌(ForecastConditionDisplay)

ppublic class ForecastConditionDisplay implements Observer, DisplayElement {

    private List<WeatherDetail> forecastDetails;//未來幾天的氣象數據詳情

    public ForecastConditionDisplay(Subject weatherData) {
        weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        if (forecastDetails != null) {
            for (WeatherDetail weatherDetail : forecastDetails) {
                System.out.println("forecast-[temperature:" + weatherDetail.getTemperature()
                        + ",humidity" + weatherDetail.getHumidity() + ",pressure" + weatherDetail.getPressure() + "]");
            }
        }
    }

    @Override
    public void update(WeatherDetail weatherDetail) {
        forecastDetails = weatherDetail.getForecastDetails();
    }
}

 

到這里,我們整個神盾氣象局的WeatherData應用就改造完成了。

兩個公告牌 CurrentConditionsDisplayForecastConditionDisplay 實現了 ObserverDisplayElement 接口。在他們的構造方法中會調用 WeatherDataregisterObserver 方法將自己注冊成觀察者,這樣被觀察者 WeatherData 就會持有觀察者的應用,並將它們保存到一個集合中。當被觀察者 WeatherData 狀態發生變化時就會遍歷這個集合,循環調用觀察者更新公告牌數據的方法。后面如果我們需要增加或者刪除公告牌,就只需要新增或者刪除實現了 ObserverDisplayElement 接口的公告牌就好了。

 

好,我們接下來測試一下利用觀察者模式改進后的程序.....

public class Client {
	public static void main(String[] args) {
		List<WeatherDetail> forecastDetail = new ArrayList<>();
		forecastDetail.add(new WeatherDetail(23.0, 0.9, 1.1, null));
		forecastDetail.add(new WeatherDetail(17.0, 0.5, 1.3, null));
		WeatherDetail weatherDetail = new WeatherDetail(22.0, 0.8, 1.2, forecastDetail);
		WeatherData weatherData = new WeatherData();
		DisplayElement current = new CurrentConditionDisplay(weatherData);
		DisplayElement forecast = new ForecastConditionDisplay(weatherData);
		weatherData.setMeasurements(weatherDetail);

		current.display();
		forecast.display();
	}
}

輸出結果

 

觀察者模式(JDK版)

我們還是用神盾氣象局的例子來實現JDK版本的觀察者模式,這樣便於比較兩者之間的差別
 
公告牌接口

public interface DisplayElement {

    void display();

}

 
氣象數據Entity

@Data
@AllArgsConstructor
public class WeatherDetail {

    private double temperature; //當前溫度
    private double humidity; //當前濕度
    private double pressure; //當前氣壓
    private List<WeatherDetail> forecastDetails;//未來幾天的氣象數據詳情

}

 
氣象數據(被觀察者)- 核心

/**
 * 天氣數據
 */
@Data
public class WeatherData extends Observable {

    private WeatherDetail weatherDetail;

    public WeatherData() {}


    public void setMeasurements(WeatherDetail weatherDetail){
        this.weatherDetail = weatherDetail;
        this.setChanged();
        notifyObservers(weatherDetail);
    }

}

 
顯示當前天氣的公告牌(CurrentConditionDisplay)

/**
 * 當前展板
 */
public class CurrentConditionDisplay implements Observer, DisplayElement {

    private double temperature; //當前溫度
    private double humidity; //當前濕度
    private double pressure; //當前氣壓

    public CurrentConditionDisplay(Observable weatherData) {
        //天氣數據
        weatherData.addObserver(this);
    }

    @Override
    public void display() {
        System.out.println("current-[temperature:" + temperature + ",humidity:" + humidity + ",pressure:" + pressure + "]");
    }


    @Override
    public void update(Observable observable, Object arg) {
        WeatherDetail weatherDetail = (WeatherDetail) arg;
        this.temperature = weatherDetail.getTemperature();
        this.humidity = weatherDetail.getHumidity();
        this.pressure = weatherDetail.getPressure();
    }
}

 
顯示未來幾天天氣的公告牌(ForecastConditionDisplay)

public class ForecastConditionDisplay implements Observer, DisplayElement {

    private List<WeatherDetail> forecastDetails;//未來幾天的氣象數據詳情

    public ForecastConditionDisplay(Observable weatherData) {
        //天氣數據
        weatherData.addObserver(this);
    }

    @Override
    public void display() {
        if (forecastDetails != null) {
            for (WeatherDetail weatherDetail : forecastDetails) {
                System.out.println("forecast-[temperature:" + weatherDetail.getTemperature()
                        + ",humidity:" + weatherDetail.getHumidity() + ",pressure:" + weatherDetail.getPressure() + "]");
            }
        }
    }

    @Override
    public void update(Observable observable, Object arg) {
        WeatherDetail weatherDetail = (WeatherDetail) arg;
        this.forecastDetails = weatherDetail.getForecastDetails();
    }

}

 
到這里,我們使用JDK自帶的API實現了觀察者模式,下面我們開始測試

public class Client {
    public static void main(String[] args) {
        List<WeatherDetail> forecastDetail = new ArrayList<>();
        forecastDetail.add(new WeatherDetail(23.0, 0.9, 1.1,null));
        forecastDetail.add(new WeatherDetail(17.0, 0.5, 1.3,null));
        WeatherDetail weatherDetail = new WeatherDetail(22.0, 0.8, 1.2, forecastDetail);
        WeatherData weatherData = new WeatherData();
        DisplayElement current = new CurrentConditionDisplay(weatherData);
        DisplayElement forecast = new ForecastConditionDisplay(weatherData);
        weatherData.setMeasurements(weatherDetail);

        current.display();
        forecast.display();
    }
}

輸出結果

 
總結:使用JDK自帶的API去實現觀察者模式固然方便,但是由於需要繼承 Observable 接口,會對被觀察者類造成限制(單繼承的局限性),其次 Observable 的代碼 從屬JDK1.0,底層還是用的相關的Vector去做安全的集合容器,個人感覺還是有點過時了,個人還是傾向於自實現觀察者模式。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM