1. 程式人生 > >Head First 設計模式之觀察者模式

Head First 設計模式之觀察者模式

什麼是觀察者模式

幫你的物件熟知現況,不會錯過該物件感興趣的事情,物件甚至在執行時間可決定是否要繼續被通知
- jdk使用最多的模式之一

氣象監測應用

系統分為三部分: 氣象站(獲取實際氣象資料的物理裝置), WeatherData物件(追蹤來自氣象站的資料,更新佈告板)和佈告板(顯示天氣狀況給使用者看)

  • 如下圖, WeatherData類,一旦氣象測量更新, 會呼叫messurementsChange()方法
  • 我們工作是實現這個方法, 讓它更新佈告板

WeatherData類

目前知道什麼

  1. WeatherData有getter方法獲取氣象資料
  2. 新的測量資料準備好後,會呼叫messurementsChange()方法(忽略如何呼叫它)
  3. 需要實現佈告板,一旦有新資料,立馬更新
  4. 系統必須可擴充套件,可以增加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();       //打印出目前觀察者列表裡的觀察者

    }
}

目前這種實現的問題

  1. 觀察者類不能主動獲取狀態
  2. 有的觀察者只需要一點點資料, 卻被推送了全部的資料
  3. !!! 按目前的,如果Subject實現類新增了狀態, 就需要重寫所有觀察者類的update()以及對它們的呼叫

    • 解決:使Subject實現類實現getter方法
    • Java內建的Observer模式兩種做法都支援

Java內建的觀察者模式

java.util.Observer , java.util.Observable(即Subject,可觀察者類)

如何將物件變為觀察者

如同之前一樣,實現(java.util.Observer)介面, 然後呼叫任何Observable物件addObserver()方法,不想當觀察者,就呼叫deleteObserver()

可觀察者如何送出通知

  • 首先,利用java.util.Observable介面產生”可觀察者”類,然後需要
    1. 呼叫setChanged()發那個發,標記狀態已改變
    2. 呼叫兩種notifyObservers()方法中的一個
      • notifyObservers()
      • notifyObservers(Object arg) 傳遞任何資料物件給每一個觀察者

觀者差如何接收通知

通之前一樣,實現了更新方法

update(Observable o, Object arg)        // 主題本身當第一個變數,讓觀察者知道是誰通知的它, 資料物件當第二個變數

原始碼連結