有這麼一個需求。
- 氣象站會發布氣象監測數據,包括(溫度,溼度,氣壓)
- 需要開發一個應用,從氣象站取得數據,並更新應用的佈告板。
- 佈告板有三類,分別是顯示當前天氣情況,氣象統計數據,天氣預報。
氣象站提供的數據結構如下,很簡單,就是溫度,溼度,氣壓的獲取,以及氣象數據變化時觸發的方法。
public class WeatherData {
private float temperature;
private float humidity;
private float pressure;
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
//氣象數據發生改變的時候執行的方法
public void measurementsChanged() {
}
}
按照通常面向過程的編程方式,會是在 measurementChanged
方法當中添加布告板的顯示更新邏輯。
public class WeatherData {
private float temperature;
private float humidity;
private float pressure;
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(); //當前天氣情況顯示
ForecastDisplay forecastDisplay = new ForecastDisplay(); //天氣預報顯示
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(); //氣象統計顯示
public void measurementsChanged() {
currentConditionDisplay.update(temperature, humidity, pressure);
forecastDisplay.update(temperature, humidity, pressure);
statisticsDisplay.update(temperature, humidity, pressure);
}
}
這樣的編程方式導致每次要添加布告板的實現都要來修改 WeatherData 的代碼。
引入觀察者模式去解決這個問題。
什麼是觀察者模式,可以通過報紙和雜誌的訂閱來解釋。
- 報社的任務就是出版報紙
- 向某家報社訂閱報紙,只要他們有新報紙出版,就會給你送來。只要你是他們的訂戶,你就會一直收到新報紙。
- 當你不想看報紙的時候,取消訂閱,他們就不會給你送新報紙來。
- 只要報社還在運營,就會有人向他們訂閱報紙或者曲線訂閱
觀察者模式將出版者稱爲 “主題(Subject)”,訂閱者稱爲“觀察者(Observer)”
同訂閱報紙的模式。
- 觀察者可以訂閱主題
- 主題的信息發生改變時,會通知觀察者,把數據傳遞給觀察者。
- 觀察者不想接受主題的信息的時候就可以取消主題。
觀察者模式定義了對象之間的一對多依賴,這樣一來,當一個對象改變狀態時,它的所有依賴都會收到通知並且自動更新。
觀察者類圖如下:
觀察者模式提供了一種對象設計,讓主題和觀察者之間松耦合。當兩個對象之間松耦合,他們依然可以交互,但是不太清楚彼此的細節。
改變主題或者觀察者的其中一方,並不會影響另一方。因爲兩者時松耦合的,所以只要他們直接的接口仍被遵守,我們就可以自由的改變他們。
松耦合的設計之所以能讓我們建立有彈性的OO系統,能夠應對變化,是因爲對象之間的互相依賴降到了最低。
根據觀察者模式,重寫氣象佈告板的程序。
類圖設計如下:
代碼程序如下:
接口的設計
//主題定義
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObserver();
}
//觀察者接口定義
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
//佈告板接口定義
public interface DisplayElement {
public void display();
}
氣象站的數據源程序
public class WeatherData implements Subject{
private ArrayList<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
public WeatherData() {
observers = new ArrayList<Observer>();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObserver() {
float temp = this.getTemperature();
float humidity = this.getHumidity();
float pressure = this.getPressure();
for (Observer o : observers) {
o.update(temp, humidity, pressure);
}
}
public void measurementsChanged() {
this.notifyObserver();
}
public void setMeasurements(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
this.measurementsChanged();
}
}
佈告板的實現
public class CurrentConditionDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private float pressure;
private Subject subject;
public CurrentConditionDisplay(Subject weatherData) {
this.subject = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
this.display();
}
@Override
public void display() {
System.out.println("this is currentConditionDisplay. temp: "+this.temperature+", humidity: "+this.humidity+", pressure:"+this.pressure);
}
}
測試程序
public class Main {
public static void main(String[] args) {
WeatherData s = new WeatherData();
Observer o = new CurrentConditionDisplay(s);
s.setMeasurements(1, 2, 3);
s.setMeasurements(4, 5, 6);
}
}
測試結果:
this is currentConditionDisplay. temp: 1.0, humidity: 2.0, pressure:3.0
this is currentConditionDisplay. temp: 4.0, humidity: 5.0, pressure:6.0
由於觀察者模式的普遍,各類語言或者框架都提供了觀察者模式的實現。
例如 java 提供了 Observable 和 Observer 兩個超類和接口。分別對應於前文提及的主題和觀察者。
使用 Observable 和 Observer 實現佈告板程序
氣象站數據源程序,繼承於 Observable 超類
package observer.example3;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;
public class WeatherData extends Observable{
private float temperature;
private float humidity;
private float pressure;
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
public WeatherData() {
}
public void measurementsChanged() {
this.setChanged(); //標識主題改變
this.notifyObservers(this); //通知所有的觀察者
}
public void setMeasurements(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
this.measurementsChanged();
}
}
佈告板程序,實現 Observer 接口,必須實現 update 方法
package observer.example3;
import java.util.Observable;
import java.util.Observer;
public class CurrentConditionDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private float pressure;
private Observable subject;
public CurrentConditionDisplay(Observable weatherData) {
this.subject = weatherData;
weatherData.addObserver(this);
}
@Override
public void display() {
System.out.println("this is currentConditionDisplay. temp: "+this.temperature+", humidity: "+this.humidity+", pressure:"+this.pressure);
}
//響應主題的推送
@Override
public void update(Observable o, Object arg) {
WeatherData w = (WeatherData)o;
this.temperature = w.getTemperature();
this.humidity = w.getHumidity();
this.pressure = w.getPressure();
this.display();
}
}
測試程序
public class Main {
public static void main(String[] args) {
WeatherData s = new WeatherData();
Observer o = new CurrentConditionDisplay(s);
s.setMeasurements(1, 2, 3);
s.setMeasurements(4, 5, 6);
}
}
測試程序
this is currentConditionDisplay. temp: 1.0, humidity: 2.0, pressure:3.0
this is currentConditionDisplay. temp: 4.0, humidity: 5.0, pressure:6.0