Java 设计模式之观察者模式
一、了解觀察者模式
1.1 什么是觀察者模式
觀察者模式定義了對象之間的一對多依賴,這樣一來,當一個對象狀態改變時,它的所有依賴者都會收到通知并自動更新。
典型的問題比如報社,只要你是他們的訂戶,他們每次有新報紙出版時,就會向你這送來,當你不想要看報紙時,取消訂閱,他們就不會再給你送報紙。
1.2 觀察者模式組成結構
- 抽象主題 (Subject):抽象主題角色把所有觀察者對象保存在一個集合里,每個主題都可以有任意數量的觀察者,抽象主題提供一個接口,可以增加和刪除觀察者對象。
- 具體主題 (ConcreteSubject):該角色將有關狀態存入具體觀察者對象,在具體主題的內部狀態發生改變時,給所有注冊過的觀察者發送通知。
- 抽象觀察者 (Observer):是觀察者的抽象類,它定義了一個更新接口,使得在得到主題更改通知時更新自己。
- 具體觀察者 (ConcrereObserver):實現抽象觀察者定義的更新接口,以便在得到主題更改通知時更新自身的狀態。
1.3 觀察者模式 UML 圖解
二、觀察者模式具體應用
2.1 問題描述
氣象觀測站系統:該系統中包含三部分,分別是氣象站 (獲取實際氣象數據的物理裝置)、WeatherData 對象 (追蹤氣象站的數據,并更新布告板) 和布告板 (顯示天氣狀況給用戶看,布告板共有兩個,分別顯示當前的溫度以及對天氣進行預告)。
2.2 問題分析
我們想要使用觀察者模式去解決這個問題,首先要分析出什么是主題,什么是觀察者,問題的關鍵是找出一對多依賴關系。這里 WeatherData 類正如所說的“一”,而“多”是用于顯示天氣情況的布告板。
WeatherData 是有狀態的對象,它包括了溫度、濕度和氣壓,而這些值都會變化,當這些值改變時,必須通知布告板,好讓它們顯示最新的數據。所以把 WeatherData 類作為主題,布告板作為觀察者。
2.3 問題分析設計圖
2.4 代碼實現
PS:代碼模塊較多,建議將這些代碼拷下來運行一遍。
抽象主題接口 Subject
package com.jas.observer;public interface Subject {/*** 注冊觀察者* * @param observer 觀察者對象*/void registObserver(Observer observer);/*** 移除觀察者** @param observer 觀察者對象*/void removeObserver(Observer observer);/*** 當主題狀態改變時,這個方法會被調用,通知所有的觀察者*/void notifyObservers(); }抽象觀察者接口 Observer
package com.jas.observer;public interface Observer {/*** 當氣象觀測值改變時,主題會把這些狀態值作為參數,傳送給觀察者* * @param temp 溫度* @param humidity 濕度* @param pressure 壓力*/void update(float temp, float humidity, float pressure); }布告信息接口 DisplayElement
package com.jas.observer;public interface DisplayElement {void display(); }具體主題類 WeatherData
package com.jas.observer;import java.util.ArrayList; import java.util.List;public class WeatherData implements Subject {private float temperature;private float humidity;private float pressure;private List<Observer> list = new ArrayList(); //使用集合保存所有的觀察者對象@Overridepublic void registObserver(Observer observer) {list.add(observer);}@Overridepublic void removeObserver(Observer observer) {int i = list.indexOf(observer);if(i >= 0 && i < list.size()){list.remove(i);}}@Overridepublic void notifyObservers() {for (int i = 0; i < list.size(); i++) { //遍歷集合中所有觀察者對象Observer observer = list.get(i);observer.update(temperature,humidity,pressure); //調用觀察者的 update() 方法}}/*** 當氣象站的數據得到更新后,通知觀察者,調用 notifyObservers() 方法*/public void measurementsChanged(){notifyObservers();}/*** 當氣象站數據改變后,設置新的數據值,并調用 measurementsChanged() 方法* * @param temperature 溫度* @param humidity 濕度* @param pressure 氣壓*/public void setMeasurements(float temperature, float humidity, float pressure){this.temperature = temperature;this.humidity = humidity;this.pressure = pressure;measurementsChanged();} }具體觀察者對象,當前天氣信息類 CurrentConditionsDisplay
package com.jas.observer;public class CurrentConditionsDisplay implements Observer, DisplayElement {private float temperature;private float humidity;private float pressure;private Subject weatherData;/*** 通過構造函數將當前觀察者注冊給具體主題對象* * @param weatherData 主題對象*/public CurrentConditionsDisplay(Subject weatherData){this.weatherData = weatherData;weatherData.registObserver(this);}/*** 布告板信息展示*/@Overridepublic void display() {System.out.println("Current conditions list : " + "溫度 = " + temperature + ", 濕度 = " + humidity + ", 氣壓 = " + pressure);}/*** 更新信息* * @param temp 溫度* @param humidity 濕度* @param pressure 壓力*/@Overridepublic void update(float temp, float humidity, float pressure) {this.temperature = temp;this.humidity = humidity;this.pressure = pressure;display();} }具體觀察者對象,預測天氣信息類 ForecastDisplay(簡單將數據減一)
package com.jas.observer;public class ForecastDisplay implements Observer, DisplayElement {private float temperature;private float humidity;private float pressure;private Subject weatherData;/*** 通過構造函數將當前觀察者注冊給主題對象** @param weatherData 主題對象*/public ForecastDisplay(WeatherData weatherData){this.weatherData = weatherData;weatherData.registObserver(this);}@Overridepublic void display() {System.out.println("Forecast conditions list : " + "溫度 = " +(temperature - 1.0) + ", 濕度 = " + (humidity - 1.0) + ", 氣壓 = " + (pressure - 1.0));}@Overridepublic void update(float temp, float humidity, float pressure) {this.temperature = temp;this.humidity = humidity;this.pressure = pressure;display();} }氣象站類 WeatherStation
package com.jas.observer;public class WeatherStation {public static void main(String[] args) {WeatherData weatherData = new WeatherData();CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);//當具體主題對象數據發生變化,所有依賴者 (觀察者) 都會實現自動數據更新weatherData.setMeasurements(18,65,30);} }/*** 輸出* Current conditions list : 溫度 = 18.0, 濕度 = 65.0, 氣壓 = 30.0* Forecast conditions list : 溫度 = 17.0, 濕度 = 64.0, 氣壓 = 29.0*/2.5 自定義觀察者模式總結
觀察者模式可以輕松實現松耦合,因為主題并不需要知道觀察者的具體類是誰,做了些什么,并且我們可以在任何時候新增觀察者。由于一個主題可能對應多個觀察者,所以當某一個觀察者出現問題時,可能導致其他的觀察者也不能正常工作。因此在一定程度上,存在著效率問題。
三、Java 內置觀察者模式
3.1 了解 Java 內置觀察者模式
java.util 包內 包含最基本的Observable 類與 Observer 接口,這和上面的 Subject 接口與 Observer 接口很類似。Observable 類與 Observer 接口使用起來更方便,因為許多的功能已經提供了。
3.2 Java 內置觀察者模式如何運作
(1)如何把對象定義為觀察者?
實現觀察者 (Observer) 接口,調用任何 Observable 對象的 addObserve() 方法。當不想要當觀察者時,調用 deleteObserve() 方法。
(2)可觀察者如何發送通知?
(3)觀察者如何接收通知?
同以前一樣,觀察者實現了 update(Observable o, Object arg) 方法,只是方法簽名不太一樣。
3.3 重寫氣象觀測站系統
主題類 WeatherData
package com.jas.jdk.observer;import java.util.Observable;public class WeatherData extends Observable {private float temperature;private float humidity;private float pressure;public void measurementsChanged(){//在通知所有觀察者之前,先調用 setChanged() 方法,用來表示狀態已經改變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;} }具體觀察者對象,當前天氣信息類 CurrentConditionsDisplay
package com.jas.jdk.observer;import java.util.Observable; import java.util.Observer;public class CurrentConditionsDisplay implements Observer, DisplayElement {private float temperature;private float humidity;private float pressure;private Observable observable;/*** 通過構造函數,將當前對象記錄為觀察者* * @param observable 主題對象*/public CurrentConditionsDisplay(Observable observable){this.observable = observable;observable.addObserver(this);}@Overridepublic void display() {System.out.println("Current conditions list : " + "溫度 = " +temperature + ", 濕度 = " + humidity + ", 氣壓 = " + pressure);}@Overridepublic void update(Observable o, Object arg) {if(o instanceof WeatherData){WeatherData weatherData = (WeatherData) o;this.temperature = weatherData.getTemperature();this.humidity = weatherData.getHumidity();this.pressure = weatherData.getPressure();display();}} }氣象站類 WeatherStation(同上)
package com.jas.jdk.observer;public class WeatherStation {public static void main(String[] args) {WeatherData weatherData = new WeatherData();CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);//ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);weatherData.setMeasurements(80,65,30.4f);} }/*** 輸出* Current conditions list : 溫度 = 80.0, 濕度 = 65.0, 氣壓 = 30.4*/3.4 Java 內置觀察者模式總結
Java 內置的觀察者模式允許觀察者有選擇的獲取數據,而不是主題對象強制將更新數據全部推送個每個觀察者。
Observable 是一個類,并不是一個接口,這意味著你繼承它的同時,不能再繼承其他的類。在 Observable 類中 setChanged() 方法被保護了起來 (protected),除非你繼承該類,否則你無法創建 Observable 實例組合到你自己的對象中來。所以它違反了一個原則:“多用組合,少用繼承”。
還有一點需要要注意的是:內置的觀察者模式,觀察者被通知的順序并不是唯一的 (上面只定義了一個觀察者),有時候并不能達到我們一開始的目的,你可以定義多個觀察者驗證一下。
根據具體的需求,如果 Java 內置的觀察者模式 API 不能滿設計,那么我們可以像剛開始那樣自己實現一套觀察者模式。
PS:點擊了解更多設計模式 http://blog.csdn.net/codejas/article/details/79236013
參考文獻
《Head First 設計模式》
總結
以上是生活随笔為你收集整理的Java 设计模式之观察者模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 抽烟会使密闭的室内pm2.5值急剧升高,
- 下一篇: Java 设计模式之装饰者模式