定義
觀察者(Observer)模式定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴者都會收到通知并自動更新。
OO設計原則:為了交互對象之間的松耦合設計而努力。
案例:氣象監測應用
概括
此系統中的三個部分是
- 氣象站(獲取實際氣象數據的物理裝置)
- WeatherData對象(追蹤來自氣象站的數據,并更新布告板)
- 布告板(顯示目前天氣狀況給用戶看)
WeatherData對象知道如何跟物理氣象站聯系,以取得更新的數據。
WeatherData對象會隨即更新三個布告板的顯示:
- 目前狀況(溫度、濕度、氣壓)、
- 氣象統計
- 天氣預報。
目標是建立一個應用,利用WeatherData對象取得數據,并更新三個布告板:目前狀況、氣象統計和天氣預報 。
設計圖
放碼過來
氣象站主程序
public class WeatherStation {public static void main(String[] args) {WeatherData weatherData = new WeatherData();CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);weatherData.setMeasurements(80, 65, 30.4f);System.out.println("---");weatherData.setMeasurements(82, 70, 29.2f);System.out.println("---");weatherData.setMeasurements(78, 90, 29.2f);System.out.println("---Remove Observer---");weatherData.removeObserver(forecastDisplay);weatherData.setMeasurements(62, 90, 28.1f);}
}
布告板
打印展現接口
public interface DisplayElement {public void display();
}
主題
public interface Subject {public void registerObserver(Observer o);public void removeObserver(Observer o);public void notifyObservers();
}
實現主題接口的氣象數據
import java.util.*;public class WeatherData implements Subject {private List<Observer> observers;private float temperature;private float humidity;private float pressure;public WeatherData() {observers = new ArrayList<Observer>();}public void registerObserver(Observer o) {observers.add(o);}public void removeObserver(Observer o) {observers.remove(o);}public void notifyObservers() {for (Observer observer : observers) {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 float getTemperature() {return temperature;}public float getHumidity() {return humidity;}public float getPressure() {return pressure;}}
觀察者
public interface Observer {public void update(float temp, float humidity, float pressure);
}
實現觀察者接口的目前狀況布告板
public class CurrentConditionsDisplay implements Observer, DisplayElement {private float temperature;private float humidity;private WeatherData weatherData;public CurrentConditionsDisplay(WeatherData weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}public void update(float temperature, float humidity, float pressure) {this.temperature = temperature;this.humidity = humidity;display();}public void display() {System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");}
}
實現觀察者接口的氣象統計布告板
public class StatisticsDisplay implements Observer, DisplayElement {private float maxTemp = 0.0f;private float minTemp = 200;private float tempSum= 0.0f;private int numReadings;private WeatherData weatherData;public StatisticsDisplay(WeatherData weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}public void update(float temp, float humidity, float pressure) {tempSum += temp;numReadings++;if (temp > maxTemp) {maxTemp = temp;}if (temp < minTemp) {minTemp = temp;}display();}public void display() {System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)+ "/" + maxTemp + "/" + minTemp);}
}
實現觀察者接口的天氣預報布告板
public class ForecastDisplay implements Observer, DisplayElement {private float currentPressure = 29.92f; private float lastPressure;private WeatherData weatherData;public ForecastDisplay(WeatherData weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}public void update(float temp, float humidity, float pressure) {lastPressure = currentPressure;currentPressure = pressure;display();}public void display() {System.out.print("Forecast: ");if (currentPressure > lastPressure) {System.out.println("Improving weather on the way!");} else if (currentPressure == lastPressure) {System.out.println("More of the same");} else if (currentPressure < lastPressure) {System.out.println("Watch out for cooler, rainy weather");}}
}
運行結果
氣象站主程序運行結果:
Current conditions: 80.0F degrees and 65.0% humidity
Avg/Max/Min temperature = 80.0/80.0/80.0
Forecast: Improving weather on the way!
---
Current conditions: 82.0F degrees and 70.0% humidity
Avg/Max/Min temperature = 81.0/82.0/80.0
Forecast: Watch out for cooler, rainy weather
---
Current conditions: 78.0F degrees and 90.0% humidity
Avg/Max/Min temperature = 80.0/82.0/78.0
Forecast: More of the same
---Remove Observer---
Current conditions: 62.0F degrees and 90.0% humidity
Avg/Max/Min temperature = 75.5/82.0/62.0
現成輪子
Java API有內置的觀察者模式。java.util包內包含最基本的Observer接口與Observable類,這和案例的Subject接口與Observer接口很相似。
Observer接口與Observable類使用上更方便,因為許多功能都已經事先準備好了。
package java.util;@Deprecated(since="9")
public interface Observer {void update(Observable o, Object arg);
}
package java.util;@Deprecated(since="9")
public class Observable {private boolean changed = false;private Vector<Observer> obs;public Observable() {obs = new Vector<>();}public synchronized void addObserver(Observer o) {if (o == null)throw new NullPointerException();if (!obs.contains(o)) {obs.addElement(o);}}public synchronized void deleteObserver(Observer o) {obs.removeElement(o);}public void notifyObservers() {notifyObservers(null);}public void notifyObservers(Object arg) {/** a temporary array buffer, used as a snapshot of the state of* current Observers.*/Object[] arrLocal;synchronized (this) {/* We don't want the Observer doing callbacks into* arbitrary code while holding its own Monitor.* The code where we extract each Observable from* the Vector and store the state of the Observer* needs synchronization, but notifying observers* does not (should not). The worst result of any* potential race-condition here is that:* 1) a newly-added Observer will miss a* notification in progress* 2) a recently unregistered Observer will be* wrongly notified when it doesn't care*/if (!changed)return;arrLocal = obs.toArray();clearChanged();}for (int i = arrLocal.length-1; i>=0; i--)((Observer)arrLocal[i]).update(this, arg);}public synchronized void deleteObservers() {obs.removeAllElements();}protected synchronized void setChanged() {changed = true;}protected synchronized void clearChanged() {changed = false;}public synchronized boolean hasChanged() {return changed;}public synchronized int countObservers() {return obs.size();}
}
注意:Observer和Observable在Java 9標記為廢棄。
Deprecated. This class and the
Observer
interface have been deprecated. The event model supported byObserver
andObservable
is quite limited, the order of notifications delivered byObservable
is unspecified, and state changes are not in one-for-one correspondence with notifications. For a richer event model, consider using thejava.beans
package. For reliable and ordered messaging among threads, consider using one of the concurrent data structures in thejava.util.concurrent
package. For reactive streams style programming, see thejava.util.concurrent.Flow
API.Link
參考資料
- 《Head First 設計模式》
- Observable (Java SE 9 & JDK 9 )
- Java 9:Observer和Observable廢棄原因及解決方案