Head First 設計模式之觀察者模式
阿新 • • 發佈:2019-02-10
什麼是觀察者模式
幫你的物件熟知現況,不會錯過該物件感興趣的事情,物件甚至在執行時間可決定是否要繼續被通知
- jdk使用最多的模式之一
氣象監測應用
系統分為三部分: 氣象站(獲取實際氣象資料的物理裝置), WeatherData物件(追蹤來自氣象站的資料,更新佈告板)和佈告板(顯示天氣狀況給使用者看)
- 如下圖, WeatherData類,一旦氣象測量更新, 會呼叫messurementsChange()方法
- 我們工作是實現這個方法, 讓它更新佈告板
目前知道什麼
- WeatherData有getter方法獲取氣象資料
- 新的測量資料準備好後,會呼叫messurementsChange()方法(忽略如何呼叫它)
- 需要實現佈告板,一旦有新資料,立馬更新
- 系統必須可擴充套件,可以增加or刪除佈告板
錯誤示範
- 在會呼叫messurementsChange()方法中新增程式碼
public class WeatherData{
// 例項變數的宣告
// ...
public void messurementsChange(){
float temp = getTemperature();
float humnidity = getHumidity();
float pressure = getPressure();
//更新佈告板
currentConditionsDisplay.update(temp,humidity,pressure);
statisticDisplay.update(temp,humidity,pressure);
forecastDisplay.update(temp,humidity,pressure);
}
// 其他WeatherData方法
// ...
}
- 哪裡不對
- 沒有針對介面程式設計
- 每加一個佈告板,就要修改程式碼
- 佈告板沒有實現一個共同的介面
- 沒有封裝易改變部分
- 沒有針對介面程式設計
出版者 + 訂閱者 = 觀察者
出版者(Subject)向訂閱者(Observer)更新資料, 訂閱者向出版者註冊訂閱,也可以退訂
觀察者模式: 定義了物件之間的一對多的依賴,這樣一來,當一個物件改變狀態時,他的所有依賴者都會收到通知並自動更新
實現方式不止一種, 但是以包含Subject和Observer介面的類設計最長見
定義觀察者模式
- interface Subject有許多觀察者 interface Observer
- 具體的觀察者ConcreteObserver必須註冊具體主題ConcreteSubject
鬆耦合的威力
- 關於觀察者的一切,主題只知道觀察者實現了某個介面(interface Observer),不需要知道其具體型別是誰
- 任何時候都可以增加新的觀察者,因為主題唯一依賴的是一個實現Observer介面的物件那個列表
- 新增or刪除觀察者,對主題沒影響,不需要修改主題程式碼
- 只需新的觀察者類實現觀察者介面,註冊為觀察者,主題會發送通知給實現觀察者介面的物件
設計原則: 為了互動物件之間的鬆耦合設計而努力
- 觀察者模式定義了物件間一對多依賴, 一個物件改變狀態,它的所有依賴者會收到通知並更新
- WeatherData就是”一”, 各種布告欄就是”多”
- 各個布告欄都有差異,但它們都應該實現相同的介面,好讓WeatherData物件把更新資料發給它們,它們可以實現多介面
實現氣象站
介面:
// import java.util.Observer;
// 先自己動手,不用java內建支援,自己建立這一切會更有彈性
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers(); // 主題狀態改變會被呼叫,以通知所有觀察者
}
public interface Observer {
public void update(float temperature, float humidity, float pressure);
}
public interface DisplayElement {
public void display(); // 佈告板需要顯示的時候呼叫此方法
}
WeatherData
import java.util.ArrayList;
public class WeatherData implements Subject{
private ArrayList<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData(){
observers = new ArrayList<Observer>();
}
@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 = 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; // 儲存對Subject的引用為了以後取消註冊使用
// System.out.println(this);
weatherData.registerObserver(this); //構造器需要weatherData物件作為註冊使用
}
@Override
public void update(float temperature, float humidity, float pressure) {
// update呼叫起來後, 將引數儲存,呼叫display方法
// 傳進來的某系引數並沒有使用
this.temperature = temperature;
this.humidity = humidity;
display();
}
@Override
public void display() {
System.out.println("CurrentConditions: " + this.temperature + "F degree and" + this.humidity + "%humidity");
}
}
可以看到update方法裡面傳進去的某些引數並沒有使用
public class ChineseConditionsDisplay implements Observer, DisplayElement{
private float temperature;
private float humidity;
private float pressure;
private Subject weatherData;
public ChineseConditionsDisplay(Subject weatherData){
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println("溫度是:" + this.temperature + "溼度是: " + this.humidity + "氣壓是: " + this.pressure);
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
}
}
測試事例
public class ObserverTest {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
ChineseConditionsDisplay chineseConditionsDisplay = new ChineseConditionsDisplay(weatherData);
weatherData.setMeasurements(80,65,30.4f);
weatherData.printObservers(); //打印出目前觀察者列表裡的觀察者
weatherData.removeObserver(chineseConditionsDisplay); //移除一個
System.out.println("移除一個物件後:");
weatherData.printObservers(); //打印出目前觀察者列表裡的觀察者
}
}
目前這種實現的問題
- 觀察者類不能主動獲取狀態
- 有的觀察者只需要一點點資料, 卻被推送了全部的資料
!!! 按目前的,如果Subject實現類新增了狀態, 就需要重寫所有觀察者類的update()以及對它們的呼叫
- 解決:使Subject實現類實現getter方法
- Java內建的Observer模式兩種做法都支援
Java內建的觀察者模式
java.util.Observer , java.util.Observable(即Subject,可觀察者類)
如何將物件變為觀察者
如同之前一樣,實現(java.util.Observer)介面, 然後呼叫任何Observable物件addObserver()方法,不想當觀察者,就呼叫deleteObserver()
可觀察者如何送出通知
- 首先,利用java.util.Observable介面產生”可觀察者”類,然後需要
- 呼叫setChanged()發那個發,標記狀態已改變
- 呼叫兩種notifyObservers()方法中的一個
- notifyObservers()
- notifyObservers(Object arg) 傳遞任何資料物件給每一個觀察者
觀者差如何接收通知
通之前一樣,實現了更新方法
update(Observable o, Object arg) // 主題本身當第一個變數,讓觀察者知道是誰通知的它, 資料物件當第二個變數