Head First設計模式:(二)觀察者模式

NO IMAGE

通過具體實現一個氣象監測系統來理解觀察者模式

此係統的三個部分是氣象站(獲取實際氣象資料的物理裝置)、WeatherData物件(追蹤來自氣象站的資料,並更新佈告板)和佈告板(顯示目前天氣狀況給使用者看)。

具體來說該應用需要:利用WeatherDate物件從氣象站取得資料,並更新三個佈告板:目前狀況、氣象統計和天氣預報。

觀察者模式定義了物件之間的一對多依賴,這樣一來,當一個物件改變狀態時,他的所有依賴者都會收到通知並自動更新。

主題和觀察者定義了一對多的關係。觀察者依賴於此主題,只要主題狀態一有變化,觀察者就會被通知。根據通知的風格,觀察者可能因此新值而更新。

關於觀察者的一切,主題只知道觀察者實現了某個介面(Observer介面)。主題不需要知道觀察者的具體類是誰、做了些什麼或其他任何細節,將物件之間的相互依賴性降到最低。符合了

設計原則:

為了互動物件之間的鬆耦合設計和努力。

結合氣象站的需求和觀察者模式的定義,得到氣象站的設計圖如下:

從圖中可以看出,有三個介面需要建立:Subject、Observer、DisplayElement

Subject:

package com.lissdy;
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObserver();
}

Observer:

package com.lissdy;
public interface Observer {
public void update(float temp,float humidity,float pressure);
}

DisplayElement:

package com.lissdy;
public interface DisplayElement {
public void display();
}

在WeatherData中實現Subject介面:

package com.lissdy;
import java.util.ArrayList;
public class WeatherData implements Subject {
private ArrayList observers;
private float temperature;
private float pressure;
private float humidity;
public WeatherData() {
observers = new ArrayList();  //加上一個ArrayList來記錄觀察者,此ArrayList是在構造器中產生的
}
public void registerObserver(Observer o) {
observers.add(o);                    //有觀察者註冊時,將其加到ArrayList後面
}
public void removeObserver(Observer o) {
int i = observers.indexOf(o);       //觀察者取消註冊時,將其從ArrayList中刪除
if (i >= 0) {
observers.remove(i);
}
}
public void notifyObserver() {
for (int i = 0; i < observers.size(); i  ) {            //通知每一位觀察者
Observer observer = (Observer) observers.get(i);
observer.update(temperature, humidity, pressure);
}
}
public void measurementsChanged() {
notifyObserver();
}
public void setMeasurements(float temperature, float humidity,
float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}

建立當前天氣狀況和溫度統計的佈告板:

當前天氣狀況:

package com.lissdy;
public class CurrentDisplay implements Observer,DisplayElement{
private float temperature;
private float humidity;
private Subject weatherDate;
public CurrentDisplay(Subject weatherDate)
{
this.weatherDate=weatherDate;
weatherDate.registerObserver(this);
}
public void update(float temperature,float humidity,float pressure)
{
this.temperature=temperature;
this.humidity=humidity;
display();
}
public void display()
{
System.out.println("目前狀況是溫度:" temperature "度     " "溼度:" humidity "%");
}
}

溫度統計:

package com.lissdy;
public class StatisticsDisplay implements Observer,DisplayElement{
private float temperature;
private Subject weatherDate;
private float max=0;
private float min=100;
private float sum=0;
private int i=0;
public StatisticsDisplay(Subject weatherDate)
{
this.weatherDate=weatherDate;
weatherDate.registerObserver(this);
}
public void update(float temperature,float humidity,float pressure)
{
this.temperature=temperature;
i  ;
sum=sum temperature;
if(temperature>max)
{
max=temperature;
}
if(temperature<min)
{
min=temperature;
}
display();
}
public void display()
{
System.out.println("平均溫度是:" (sum/i) "最高溫度是:" max "最低溫度是:" min);
}
}

建立一個測試程式:

package com.lissdy;
public class WeatherStation {
public static void main(String[] args)
{
WeatherData weatherData=new WeatherData();   //建立一個WeatherData物件
CurrentDisplay currentDisplay=new CurrentDisplay(weatherData);  //建立佈告板,並把WeatherData傳給它們
StatisticsDisplay statisticsDisplay=new StatisticsDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);  //模擬新的氣象測量
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}

執行結果:


以上採用自己構建觀察者模式的方法完成了氣象站系統。但是,JAVA API內有內建的觀察者模式。

java .util包內包含了最基本的Observer介面和Observable類,這和之前自己構造的Observer介面和Subject介面很相似。

若使用java內建觀察者模式實現氣象站系統,其設計圖為:

利用內建的支援重做氣象站:
首先,把WeatherData改成使用java.util.Observable

package com.lissdy;
import java.util.Observable;
public class WeatherData extends Observable {
private float temperature;
private float pressure;
private float humidity;
public WeatherData() {
} // 不需要再使用ArrayList來記錄觀察者了,API代勞
public void measurementsChanged() {
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;
}
}

重做佈告板:

當前天氣:

package com.lissdy;
import java.util.Observer;
import java.util.Observable;
public class CurrentDisplay implements Observer, DisplayElement {
Observable observable;
private float temperature;
private float humidity;
public CurrentDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
public void update(Observable obs, Object arg) {
if (obs instanceof WeatherData) {
WeatherData weatherData = (WeatherData) obs;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
public void display() {
System.out.println("目前狀況是溫度:"   temperature   "度     "   "溼度:"
humidity   "%");
}
}

氣溫統計:

package com.lissdy;
import java.util.Observable;
import java.util.Observer;
public class StatisticsDisplay implements Observer, DisplayElement {
Observable observable;
private float temperature;
private float max = 0;
private float min = 100;
private float sum = 0;
private int i = 0;
public StatisticsDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
public void update(Observable obs, Object args) {
if (obs instanceof WeatherData) {
WeatherData weatherData = (WeatherData) obs;
this.temperature = weatherData.getTemperature();
i  ;
sum = sum   temperature;
if (temperature > max) {
max = temperature;
}
if (temperature < min) {
min = temperature;
}
display();
}
}
public void display() {
System.out.println("平均溫度是:"   (sum / i)   "最高溫度是:"   max   "最低溫度是:"
min);
}
}

執行結果:

注意和之前得到的結果相同,但是佈告板的排列順序不同。這是由於自己實現的觀察者模式和JAVA API中的notifyObservers()方法實現方式不同造成的。

java.util.Observable是一個類而不是一個介面,違反了針對介面程式設計,而非針對實現程式設計的設計原則。

在JavaBeans和Swing中也都實現了觀察者模式,例如一個按鈕繫結兩個監聽器,按下按鈕時,兩個監聽器都被觸發。