設計模式(十八)——觀察者模式(JDK Observable原始碼分析)

十四lin發表於2021-02-07

1 天氣預報專案需求,具體要求如下:

1) 氣象站可以將每天測量到的溫度,溼度,氣壓等等以公告的形式釋出出去(比如釋出到自己的網站或第三方)。

2) 需要設計開放型 API,便於其他第三方也能接入氣象站獲取資料。

3) 提供溫度、氣壓和溼度的介面

4) 測量資料更新時,要能實時的通知給第三方

天氣預報設計方案 1-普通方案

 WeatherData 類

  • 傳統的設計方案

 

 

  •  程式碼實現
package com.lin.observer;

/**
 * 顯示當前天氣情況(可以理解為氣象站的網站)
 * @Description: 
 * @author LinZM  
 * @date 2021-2-7 12:49:27 
 * @version V1.8
 */
public class CurrentConditions {

    // 溫度,氣壓,溼度
    private float temperatrue;
    
    private float pressure;
    
    private float humidity;

    // 更新天氣情況
    public void update(float temperature, float pressure, float humidity) {
        this.temperatrue = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        display();
    }
    
    public void display(){
        System.out.println("===Today's temperature: "+temperatrue+"===");
        System.out.println("===Today's pressure: "+pressure+"===");
        System.out.println("===Today's humidity: "+humidity+"===");
    }
    
}
package com.lin.observer;

/*
 *     類是核心 1. 包含最新的天氣情況資訊 2. 含有 CurrentConditions 物件 3. 當資料有更新時,就主動的呼叫
 *     CurrentConditions 物件 update 方法(含 display), 這樣他們(接入方)就看到最新的資訊
 */

public class WeatherData {
    
    private float temperatrue;
    private float pressure;
    private float humidity;
    private CurrentConditions currentConditions;
    
    //加入新的第三方

    public WeatherData(CurrentConditions currentConditions) {
        this.currentConditions = currentConditions;
    }

    public float getTemperature() {
        return temperatrue;
    }

    public float getPressure() {
        return pressure;
    }

    public float getHumidity() {
        return humidity;
    }

    public void dataChange() {

    //呼叫 接入方的 update
        currentConditions.update(getTemperature(), getPressure(), getHumidity());
    }

    //當資料有更新時,就呼叫 setData
    public void setData(float temperature, float pressure, float humidity) {
        this.temperatrue = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        //呼叫 dataChange, 將最新的資訊 推送給 接入方 currentConditions
        dataChange();
    }
}
package com.lin.observer;

public class Client {

    public static void main(String[] args) {
        
        CurrentConditions currentConditions = new CurrentConditions();
        WeatherData weatherData = new WeatherData(currentConditions);
        weatherData.setData(10, 20, 3);
        
    }
}

  • 問題分析

1) 其他第三方接入氣象站獲取資料的問題

2) 無法在執行時動態的新增第三方 (新浪網站)

3) 違反 ocp 原則=>觀察者模式

// WeatherData 中,當增加一個第三方,都需要建立一個對應的第三方的公告板物件,並加入到 dataChange, 不利於維護,也不是動態加入

public void dataChange() {

currentConditions.update(getTemperature(), getPressure(), getHumidity());

}

3 觀察者模式原理

1) 觀察者模式類似訂牛奶業務

2) 奶站/氣象局:Subject

3) 使用者/第三方網站:Observer

  • Subject:登記註冊、移除和通知

1) registerObserver 

2) removeObserver 

3) notifyObservers() 通知所有的註冊的使用者,根據不同需求,可以是更新資料,讓使用者來取,也可能是實施推送, 看具體需求定

  • Observer:接收輸入
  • 觀察者模式:物件之間多對一依賴的一種設計方案,被依賴的物件為 Subject,依賴的物件為 Observer,Subject

通知 Observer 變化,比如這裡的奶站是 Subject,是 1 的一方。使用者時 Observer,是多的一方。

4 觀察者模式解決天氣預報需求

類圖說明

 

 

 

 

程式碼實現

package com.lin.observer.plus;

public interface Subject {

    void registerObserver(Observer o);
    
    void removeObserver(Observer o);
    
    void notifyObserver();
    
}

 

package com.lin.observer.plus;

import java.util.ArrayList;

/*
 *     類是核心 1. 包含最新的天氣情況資訊 2. 含有 CurrentConditions 物件 3. 當資料有更新時,就主動的呼叫
 *     CurrentConditions 物件 update 方法(含 display), 這樣他們(接入方)就看到最新的資訊
 */

public class WeatherData implements Subject{
    
    private float temperatrue;
    private float pressure;
    private float humidity;
    private ArrayList<Observer> observers;
    
    //加入新的第三方

    public WeatherData() {
        observers = new ArrayList<Observer>();
    }

    public float getTemperature() {
        return temperatrue;
    }

    public float getPressure() {
        return pressure;
    }

    public float getHumidity() {
        return humidity;
    }

    public void dataChange() {

    //呼叫 接入方的 update
        //currentConditions.update(getTemperature(), getPressure(), getHumidity());
        notifyObserver();
    }

    //當資料有更新時,就呼叫 setData
    public void setData(float temperature, float pressure, float humidity) {
        this.temperatrue = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        //呼叫 dataChange, 將最新的資訊 推送給 接入方 currentConditions
        dataChange();
    }

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

    @Override
    public void removeObserver(Observer o) {
        if(observers.contains(o)) {
            observers.remove(o);
        }
    }

    @Override
    public void notifyObserver() {
        for (int i = 0; i < observers.size(); i++) {
            observers.get(i).update(temperatrue, pressure, humidity);
        }
    }
}

 

package com.lin.observer.plus;

public interface Observer {

    void update(float temperatrue, float pressure, float humidity);
    
}

 

package com.lin.observer.plus;

public class CurrentConditions implements Observer{

        // 溫度,氣壓,溼度
        private float temperatrue;
        
        private float pressure;
        
        private float humidity;

        // 更新天氣情況
        public void update(float temperature, float pressure, float humidity) {
            this.temperatrue = temperature;
            this.pressure = pressure;
            this.humidity = humidity;
            display();
        }
        
        public void display(){
            System.out.println("===Today's temperature: "+temperatrue+"===");
            System.out.println("===Today's pressure: "+pressure+"===");
            System.out.println("===Today's humidity: "+humidity+"===");
        }
        
}

 

package com.lin.observer.plus;

public class BaiDu implements Observer{

    // 溫度,氣壓,溼度
    private float temperatrue;

    private float pressure;

    private float humidity;

    // 更新天氣情況
    public void update(float temperature, float pressure, float humidity) {
        this.temperatrue = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        display();
    }

    public void display() {
        System.out.println("===BaiDu's temperature: " + temperatrue + "===");
        System.out.println("===BaiDu's pressure: " + pressure + "===");
        System.out.println("===BaiDu's humidity: " + humidity + "===");
    }

    
}

 

package com.lin.observer.plus;

public class Client {

    public static void main(String[] args) {
        // 建立一個WeatherData
        WeatherData weatherData = new WeatherData();
        
        // 建立觀察者
        CurrentConditions currentConditions = new CurrentConditions();
        BaiDu baiDu = new BaiDu();
        
        // 註冊
        weatherData.registerObserver(currentConditions);
        weatherData.registerObserver(baiDu);
        
        // 測試
        System.out.println("通知各個註冊的觀察者:");
        weatherData.setData(23, 12, -0.4f);
    }
}

 觀察者模式的好處

1) 觀察者模式設計後,會以集合的方式來管理使用者(Observer),包括註冊,移除和通知。

2) 這樣,我們增加觀察者(這裡可以理解成一個新的公告板),就不需要去修改核心類 WeatherData 不會修改程式碼, 遵守了 ocp 原則。

觀察者模式在 Jdk 應用的原始碼分析

1) Jdk  Observable 類就使用了觀察者模式

2) 程式碼分析+模式角色分析

 

3) 模式角色分析

  • Observable 的作用和地位等價於 我們前面講過 Subject
  • Observable 是類,不是介面,類中已經實現了核心的方法 ,即管理 Observer 的方法 add.. delete .. notify...
  • Observer 的作用和地位等價於我們前面講過的 Observer, update
  • Observable  Observer 的使用方法和前面講過的一樣,只是 Observable 是類,通過繼承來實現觀察者模式

 

僅供參考,有錯誤還請指出!

有什麼想法,評論區留言,互相指教指教。

相關文章