設計模式之觀察者模式


定義:

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

設計原則

  • 封裝變化
  • 多用組合,少用繼承
  • 針對接口編程,不針對實現編程
  • 為交互對象之間的松耦合設計而努力

觀察者模式提供了一種對象設計,讓主題和觀察者之間松耦合。

氣象站系統

此系統中的三個部分是氣象站(獲取實際氣象數據的物理裝置)、WeatherData對象(追蹤來自氣象站的數據,並更新布告板)和布告板(顯示目前天氣狀況給用戶看)。

image.png

工作:利用WeatherData對象取得數據,並更新三個布告板:目前狀況、氣象統計和天氣預報。

定義觀察者模式:類圖

image.png
利用觀察者模式,主題是具有狀態的對象, 並且可以控制這些狀態。也就是說,有“一個”具有狀態的主題。另一方面,觀察者使用這些狀態,雖然這些狀態並不屬於他們。有許多的觀察者,依賴主題來告訴他們狀態何時改變了。這就產生一個關系: “一個”主題對“多個”觀察者的關系。
主題只知道觀察者實現了某個接口(也就是Observer接口)。主題不需要知道觀察者的具體類是誰、做了些什么或其他任何細節。
任 何 時 候 我 們 都 可 以 增 加 新 的 觀 察 者 。 因 為 主 題 唯 一 依 賴 的 東 西 是 一 個 實 現Observer接口的對象列表,所以我們可以隨時增加觀察者。
改變主題或觀察者其中一方,並不會影響另一方。因為兩者是松耦合的,所以只要他們之間的接口仍被遵守,我們就可以自由地改變他們。

設計氣象站

實現氣象站

image.png

public interface Subject {
    // 這兩個方法都需要一個觀察者作為變量,該觀察者是用來注冊或被刪除的。
    void registerObserver(Observer c);
    void removerObserver(Observer o);

    // 當主題狀態改變時,這個方法會被調用,以通知所有的觀察者。
    void notifyObservers();
}


public interface Observer {
    // 當氣象觀測值改變時,主題會把這些狀態值當作方法的參數,傳送給觀察者
    void update(float temp, float humidity, float pressure);
}

public interface DisplayElement {
    // 當布告板需要顯示時,調用此方法
    void display();
}
public class WeatherData implements Subject{
    private ArrayList<Observer> observers; // ArrayList來紀錄觀察者
    private float temperature;
    private float humidity;
    private float pressure;
    
    public WeatherData() {
        observers = new ArrayList<>();
    }

    @Override
    public void registerObserver(Observer c) {
        observers.add(c);
    }

    @Override
    public void removerObserver(Observer o) {
        int i = observers.indexOf(o);
        if (i >= 0) {
            observers.remove(i);
        }
    }

    // 把狀態告訴每一個觀察者。因為觀察者都實現了update()
    @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;

    // 構造器需要 weatherData對象(也就是主題)作為注冊之用。
    public CurrentConditionsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    // display()方法就只是把最近的溫度和濕度顯示出來。
    @Override
    public void display() {
        System.out.println("Current conditions:" + temperature
                + "F degrees and " + humidity + "% humidity");
    }

    // 當update()被調用時,我們把溫度和濕度保存起來,然后調用display()。
    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        display();
    }
}

測試程序

public class WeatherStation {

    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();

        CurrentConditionsDisplay currentDisplay =
                new CurrentConditionsDisplay(weatherData);
        
        // 建立三個布告板 ,並把WeatherData對象傳給它們。
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
        ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
        HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(weatherData);

        // 模擬新的氣象測量
        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);
        weatherData.setMeasurements(78, 90, 29.2f);
    }
}

image.png

使用Java內置的觀察者模式

image.png

**如何把對象變成觀察者 **
實現觀察者接口(java.uitl.Observer),然后調用任何Observable對象的addObserver()方法。不想再當觀察者時,調用deleteObserver()方法

**可觀察者要如何送出通知 **
擴展java.util.Observable接口產生“可觀察者”類,然后,需要兩個步驟:

  1. 先調用setChanged()方法,標記狀態已經改變的事實。
  2. 然后調用兩種notifyObservers()方法。

**觀察者如何接收通知 **
觀察者實現了更新的方法

import java.util.Observable;
import java.util.Observer;

public class WeatherData extends Observable {
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {  // 不再需要自己存儲觀察者了
    }

    // 不在需要追蹤觀察者了,也不需要管理注冊與刪除(讓超類干)。所以把注冊、添加、通知的相關代碼刪除
    public void measurementsChanged() {
        setChanged();       // 在 notifyObservers 之前設置狀態
        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;
    }
}
import java.util.Observable;//導入Observerable
import java.util.Observer;
//實現java.util.Observer接口
public class CurrentConditionsDisplay implements Observer, DisplayElement {
     Observerable observable;
     private float temperature;
     private float humidity;
     public CurrentConditionsDisplay(Observable observable) {
         // 將Observer當參數,並將CurrentConditionsDisplay對象登記為觀察者
         this.observable=observable;
         observable.addObserver(this);
     }
     @Override
     public void display() {
         //顯示當前溫濕度狀況
         System. out. println("Current conditions:"+ temperature+"F degrees and"t humidity +"% humidity");
     }
     @Override
     public void update(Observable obs, Object arg) {
         // TODO 自動生成的方法存根
         if(obs instanceof WeatherData){
             WeatherData weatherData=(WeatherData)obs;
             this.temperature=weatherData.getTemperature();
             this.humidity=weatherData.getHumidity();
             display();
         }
    }
}

注:java.util.Observable的實現有許多問題,限制了它的使用和復用。

Observable是一個“類”,你必須設計一個類繼承它。如果某類想同時具有Observable類和另一個超類的行為,就會陷入兩難,畢竟Java不支持多重繼承。這限制了Observable的復用潛力 。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM