目錄
- 基本介紹
- 觀察者模式
- 觀察者模式(JDK版)
基本介紹
觀察者模式(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應用就改造完成了,
兩個公告牌 CurrentConditionsDisplay 和 ForecastConditionDisplay 實作了 Observer 和 DisplayElement 介面,在他們的構造方法中會呼叫 WeatherData 的 registerObserver 方法將自己注冊成觀察者,這樣被觀察者 WeatherData 就會持有觀察者的應用,并將它們保存到一個集合中,當被觀察者 WeatherData 狀態發生變化時就會遍歷這個集合,回圈呼叫觀察者更新公告牌資料的方法,后面如果我們需要增加或者洗掉公告牌,就只需要新增或者洗掉實作了 Observer 和 DisplayElement 介面的公告牌就好了,
好,我們接下來測驗一下利用觀察者模式改進后的程式.....
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 = https://www.cnblogs.com/dwlovelife/p/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 = https://www.cnblogs.com/dwlovelife/p/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去做安全的集合容器,個人感徑訓是有點過時了,個人還是傾向于自實作觀察者模式,
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/3107.html
標籤:設計模式
上一篇:設計模式(9) 裝飾模式
下一篇:策略模式
