1. 程式人生 > >一個demo徹底搞懂觀察者模式

一個demo徹底搞懂觀察者模式

一、介紹

觀察者模式也被稱為釋出-訂閱(Publish/Subscribe)模式,它屬於行為型模式的一種。觀察者模式定義了一種一對多的依賴關係,一個主題物件可被多個觀察者物件同時監聽。當這個主題物件狀態變化時,會通知所有觀察者物件並作出相應處理邏輯。

二、UML圖

三、入門案例分析 ~ ~ (無使用觀察者模式)

上面的內容比較抽象,這裡用一個案例加深對觀察者模式的理解!

需求:

  • Internet氣象站專案:

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

測量資料更新時需時時通知給第三方 (通知)

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

  • WeatherData類

  • 通常的設計方案

1) 定義一個第三方公司的模板類

package com.dgut.edu.cn;

/**
 * 第三方公司的模板類
 */
public class CurrentCondition {
    // 環境溫度
    private float temperature;
    // 環境氣壓
    private float pressure;
    // 環境溼度
    private float humidity;

    /**
     *更新資訊
     * @param temperature
     * @param pressure
     * @param humidity
     */
    public void update(float temperature,float pressure,float humidity){
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        display();
    }

    /**
     * 展示資料使用 —— 每個公司的模板不一樣
     */
    public void display(){
        System.out.println("***** the day temperature is " + temperature + "******");
        System.out.println("***** the day pressure is " + pressure + "******");
        System.out.println("***** the day humidity is " + humidity + "******");
    }
}

2) 定義氣象公司的介面類

package com.dgut.edu.cn;

public class WeatherData {
    // 環境溫度
    private float temperature;
    // 環境氣壓
    private float pressure;
    // 環境溼度
    private float humidity;

    public WeatherData() {
    }

    public float getTemperature() {
        return temperature;
    }

    public void setTemperature(float temperature) {
        this.temperature = temperature;
    }

    public float getPressure() {
        return pressure;
    }

    public void setPressure(float pressure) {
        this.pressure = pressure;
    }

    public float getHumidity() {
        return humidity;
    }

    public void setHumidity(float humidity) {
        this.humidity = humidity;
    }

    // 溫度、溼度、氣壓發生改變時,觸發函式
    public void dataChange(){
       
    }
}

3) 因為氣象站的介面中,我們需要去呼叫第三方介面更新資料方法,故引入該第三方公司的實現類,並且在構造方法中進行例項化

在WeatherData中,加入以下程式碼:

// 別人的介面
private CurrentCondition currentCondition;

public WeatherData(CurrentCondition currentCondition) {
    this.currentCondition = currentCondition;
}

4) 在氣象站介面中,寫一個跟新資料的方法,模擬氣象站自動更新資料


public void setData(float temperature,float pressure,float humidity){
    this.temperature = temperature;
    this.pressure = pressure;
    this.humidity = humidity;
    dataChange();
}

5) 在dataChange方法中去通知第三方介面更新資料

// 溫度、溼度、氣壓發生改變時,觸發函式
public void dataChange(){
    currentCondition.update(temperature,pressure,humidity);
}

6)  寫測試案例如下:

public class Application {
    public static void main(String[] args) {
        CurrentCondition currentCondition = new CurrentCondition();
        WeatherData weatherData = new WeatherData(currentCondition);
        weatherData.setData(20,50,100);
    }
}

7) 執行結果:

***** the day temperature is 20.0******
***** the day pressure is 50.0******
***** the day humidity is 100.0******
  • 分析上面存在的問題

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

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

【解釋:】在上述設計中,在Weather中引入currentCondition的成員變數,那如果有新的第三方模板接入,我們又得建立一個新第三方模板變數,手動去新增更新資料的程式碼。這樣子就很麻煩,在擴充套件性方面很弱。

  • 新的一個解決方案:

根據這種訂閱/通知的設計思路,在Weather中提供一個註冊服務、移除服務、更改服務的方法,在第三方模板中,定義一個更新資料的介面Observer,在模板類去實現這個介面。而在Weather中,不在存入具體的子類,而是存一個Observer即可,這樣就可以完成在不改變WeatherData介面中,動態去新增多個訂閱者。

基於以上分析

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

Observer:接收輸入

【總結】到這裡就基本能理解觀察者模式的UML圖,提供註冊、移除、更改的介面,以及nodify展示資料的介面

  • 入門案例進一步分析 ~ ~ (觀察者模式)

用觀察者模式設計重新設計的方案

1、寫observer介面

public interface Observer {
    public void update(float temperature,float pressure,float humidity);
}

2、寫Subject介面


public interface Subject {
    public void registerObserver(Observer observer);
    public void removeObserver(Observer observer);
    public void notifyObserver();
}

3、第三方模板類繼承該Observer介面

public class CurrentCondition implements Observer {
    // 環境溫度
    private float temperature;
    // 環境氣壓
    private float pressure;
    // 環境溼度
    private float humidity;

    /**
     *更新資訊
     * @param temperature
     * @param pressure
     * @param humidity
     */
    public void update(float temperature,float pressure,float humidity){
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        display();
    }

    public void display(){
        System.out.println("***** the day temperature is " + temperature + "******");
        System.out.println("***** the day pressure is " + pressure + "******");
        System.out.println("***** the day humidity is " + humidity + "******");
    }


}
public class ForcastCondition implements Observer{
    // 環境溫度
    private float temperature;
    // 環境氣壓
    private float pressure;
    // 環境溼度
    private float humidity;

    /**
     *更新資訊
     * @param temperature
     * @param pressure
     * @param humidity
     */
    public void update(float temperature,float pressure,float humidity){
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        display();
    }

    public void display(){
        System.out.println("***** Tomorrow temperature is " + temperature + "******");
        System.out.println("***** Tomorrow pressure is " + pressure + "******");
        System.out.println("***** Tomorrow humidity is " + humidity + "******");
    }


}


4、Weather類繼承該Subject介面

package cn;

import java.util.ArrayList;

public class WeatherDataSt implements Subject{
    // 環境溫度
    private float temperature;
    // 環境氣壓
    private float pressure;
    // 環境溼度
    private float humidity;


    private ArrayList<Observer> observerArrayList = new ArrayList<Observer>();

  

    public WeatherDataSt() {
    }

    public float getTemperature() {
        return temperature;
    }

    public void setTemperature(float temperature) {
        this.temperature = temperature;
    }

    public float getPressure() {
        return pressure;
    }

    public void setPressure(float pressure) {
        this.pressure = pressure;
    }

    public float getHumidity() {
        return humidity;
    }

    public void setHumidity(float humidity) {
        this.humidity = humidity;
    }

    // 溫度、溼度、氣壓發生改變時,觸發函式
    public void dataChange(){
        notifyObserver();
    }

    public void setData(float temperature,float pressure,float humidity){
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        dataChange();
    }

    @Override
    public void registerObserver(Observer observer) {
        observerArrayList.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        if(observerArrayList.contains(observer))
            observerArrayList.remove(observer);
    }

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

5、寫測試案例如下

package cn;

public class Application {
    public static void main(String[] args) {
        CurrentCondition currentCondition = new CurrentCondition();
        ForcastCondition forcastCondition =  new ForcastCondition();

        WeatherDataSt weatherData = new WeatherDataSt(currentCondition);


        weatherData.registerObserver(currentCondition);
        weatherData.registerObserver(forcastCondition);
        weatherData.setData(10,480,10);
        System.out.println("========================================");
        weatherData.removeObserver(currentCondition);
        weatherData.setData(20,60,700);
    }
}


6、執行結果

***** the day temperature is 10.0******
***** the day pressure is 480.0******
***** the day humidity is 10.0******
***** Tomorrow temperature is 10.0******
***** Tomorrow pressure is 480.0******
***** Tomorrow humidity is 10.0******
========================================
***** Tomorrow temperature is 20.0******
***** Tomorrow pressure is 60.0******
***** Tomorrow humidity is 700.0******


入門案例最終分析 ~ ~ (觀察者模式)— Java自帶類至此,我們已經實現了服務的動態訂閱和移除。

1、Java內建的觀察者

Observable

Observer

2、用Java內建觀察者重新設計該專案

3、內建觀察者的注意點

Observable是類而不是介面

  1. 程式碼例項如下:

1、模板類實現OBserver介面

package cn;

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

public class CurrentCondition implements Observer{
    // 環境溫度
    private float temperature;
    // 環境氣壓
    private float pressure;
    // 環境溼度
    private float humidity;


    @Override
    public void update(Observable o, Object arg) {
        this.temperature = ((WeatherData.Data)(arg)).getTemperature();
        this.pressure = ((WeatherData.Data)(arg)).getPressure();
        this.humidity = ((WeatherData.Data)(arg)).getHumidity();
        display();
    }

    public void display(){
        System.out.println("***** the day temperature is " + temperature + "******");
        System.out.println("***** the day pressure is " + pressure + "******");
        System.out.println("***** the day humidity is " + humidity + "******");
    }


}
package cn;

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

public class ForcastCondition implements Observer {
    // 環境溫度
    private float temperature;
    // 環境氣壓
    private float pressure;
    // 環境溼度
    private float humidity;


    @Override
    public void update(Observable o, Object arg) {
        this.temperature = ((WeatherData.Data)(arg)).getTemperature();
        this.pressure = ((WeatherData.Data)(arg)).getPressure();
        this.humidity = ((WeatherData.Data)(arg)).getHumidity();
        display();
    }


    public void display(){
        System.out.println("***** Tomorrow temperature is " + temperature + "******");
        System.out.println("***** Tomorrow pressure is " + pressure + "******");
        System.out.println("***** Tomorrow humidity is " + humidity + "******");
    }


}

2、Weacher繼承OBserverable介面

package cn;

import java.util.Observable;

public class WeatherData extends Observable{
    // 環境溫度
    private float temperature;
    // 環境氣壓
    private float pressure;
    // 環境溼度
    private float humidity;


    public WeatherData() {
    }

    public float getTemperature() {
        return temperature;
    }

    public void setTemperature(float temperature) {
        this.temperature = temperature;
    }

    public float getPressure() {
        return pressure;
    }

    public void setPressure(float pressure) {
        this.pressure = pressure;
    }

    public float getHumidity() {
        return humidity;
    }

    public void setHumidity(float humidity) {
        this.humidity = humidity;
    }

    // 溫度、溼度、氣壓發生改變時,觸發函式
    public void dataChange(){
        this.setChanged();
       this.notifyObservers(new Data(temperature,pressure,humidity));
    }

    public void setData(float temperature,float pressure,float humidity){
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        dataChange();
    }

    class Data{
        // 環境溫度
        private float temperature;
        // 環境氣壓
        private float pressure;
        // 環境溼度
        private float humidity;


        public Data(float temperature, float pressure, float humidity) {
            this.temperature = temperature;
            this.pressure = pressure;
            this.humidity = humidity;
        }

        public float getTemperature() {
            return temperature;
        }

        public void setTemperature(float temperature) {
            this.temperature = temperature;
        }

        public float getPressure() {
            return pressure;
        }

        public void setPressure(float pressure) {
            this.pressure = pressure;
        }

        public float getHumidity() {
            return humidity;
        }

        public void setHumidity(float humidity) {
            this.humidity = humidity;
        }

        @Override
        public String toString() {
            return "Data{" +
                    "temperature=" + temperature +
                    ", pressure=" + pressure +
                    ", humidity=" + humidity +
                    '}';
        }
    }
}


3、測試程式碼如下:

package cn;

public class Application {
    public static void main(String[] args) {
        CurrentCondition currentCondition = new CurrentCondition();
        ForcastCondition forcastCondition = new ForcastCondition();

        WeatherData weatherData = new WeatherData();
        weatherData.addObserver(currentCondition);
        weatherData.addObserver(forcastCondition);
        weatherData.setData(111,222,333);
        weatherData.deleteObserver(currentCondition);
        weatherData.setData(1,2,3);
    }
}


4、測試結果如下:

***** Tomorrow temperature is 111.0******
***** Tomorrow pressure is 222.0******
***** Tomorrow humidity is 333.0******
***** the day temperature is 111.0******
***** the day pressure is 222.0******
***** the day humidity is 333.0******
***** Tomorrow temperature is 1.0******
***** Tomorrow pressure is 2.0******
***** Tomorrow humidity is 3.0******