大話--觀察者模式

10000_Hours發表於2020-07-25

觀察者模式:

定義:

定義了物件之間的一對多依賴,讓多個觀察者(Observer)同時監聽某一主題物件(Subject)。當主題物件變化時,所有觀察者都會收到通知並更新。
核心程式碼是在抽象類中有一個ArrayList用來存放觀察者們。

使用場景:

一個抽象模型有兩個方面,其中一個方面依賴於另一個方面。將這些方面封裝在獨立的物件中使它們可以各自獨立地改變和複用。
一個物件的改變將導致其他一個或多個物件也發生改變,而不知道具體有多少物件將發生改變,可以降低物件之間的耦合度。
一個物件必須通知其他物件,而並不知道這些物件是誰。
需要在系統中建立一個觸發鏈,A物件的行為將影響B物件,B物件的行為將影響C物件……,可以使用觀察者模式建立一種鏈式觸發機制。

優點:

觀察者和被觀察者是抽象耦合的;建立了一套觸發機制

缺點:

1、如果一個被觀察者物件有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
2、如果在觀察者和觀察目標之間有迴圈依賴的話,觀察目標會觸發它們之間進行迴圈呼叫,可能導致系統崩潰。
3、觀察者模式沒有相應的機制讓觀察者知道所觀察的目標物件是怎麼發生變化的,而僅僅只是知道觀察目標發生了變化。

組成:

主題物件(Subject)具有註冊和移除觀察者、並通知所有觀察者的功能,主題是通過維護一張觀察者列表來實現這些操作的。
觀察者(Observer)的註冊功能需要呼叫主題的 registerObserver() 方法。

類圖:

原始普通方案的類圖

原始方案的缺點:
無法在執行時動態地新增第三方(網站等)
違反ocp原則(在WheatherData中,每增加一個第三方都需要建立一個對應的第三方的公告板物件,並加入到dataChange,不利於維護,也不是動態加入)

使用觀察者模式的原始碼

JDK的Observable類

  public interface Observer{
        void update(Observable o,Object arg);
}
Observable就等同於前文中的Subject,但它是一個類不是介面,類中已經實現了核心方法(add,delete,notify),通過繼承來實現觀察者模式。
Observer等同於前文中的Observer,有update方法。

java.util.EventListener
javax.servlet.http.HttpSessionBindingListener

具體實現

1.建立Subject介面和Observer介面

//主題物件介面,讓WeatherData來實現
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 pressure,float humidity);

}

2.建立Subject介面的具體實現類WeatherData

package Observer;

import java.util.ArrayList;

/**
 * @title: WeatherData
 * @Author yzhengy
 * @Date: 2020/7/25 18:27
 * @Question:
 * 本類是核心
 * 1. 包含最新的天氣情況資訊
 * 2. 含有觀察者集合,使用ArrayList管理
 * 3. 當資料有更新時,就主動的呼叫ArrayList, 通知所有的(接入方)就看到最新的資訊
 */
public class WeatherData implements Subject{

    private float temperature;
    private float pressure;
    private float humidity;

    private ArrayList<Observer> observers;//觀察者集合

    //加入新的第三方
    public WeatherData() {
        observers = new ArrayList<Observer>();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getPressure() {
        return pressure;
    }

    public float getHumidity() {
        return humidity;
    }

    public void dataChange(){
        notifyObservers();//呼叫接入方的update方法
    }

    //有資料更新時,就呼叫setData
    public void setData(float temperature,float pressure,float humidity){
        this.temperature = temperature;
        this.pressure = pressure;
        this.humidity = humidity;
        //呼叫dataChange,將最新的資料推送給接入方CurrentCondition
        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 notifyObservers() {
        //遍歷所有的觀察者,並通知
        for (int i = 0; i <observers.size() ; i++) {
            observers.get(i).update(this.temperature,this.pressure,this.humidity);
        }
    }
}

3.建立Observer介面的具體實現類

package Observer;

/**
 * @title: CurrentCondition
 * @Author yzhengy
 * @Date: 2020/7/25 18:41
 * @Question:
 */
public class CurrentCondition implements Observer{

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

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

    //顯示
    private void display() {
        System.out.println("***Today mTemperature: " + temperature + "***");
        System.out.println("***Today mPressure: " + pressure + "***");
        System.out.println("***Today mHumidity: " + humidity + "***");
    }
}

4.最後寫一個Client類用來呼叫介面並測試

package Observer;

import javafx.beans.binding.When;

/**
 * @title: Client
 * @Author yzhengy
 * @Date: 2020/7/25 18:45
 * @Question:
 */
public class Client {

    public static void main(String[] args) {
        //建立一個WeatherData
        WeatherData weatherData = new WeatherData();

        //建立觀察者
        CurrentCondition currentCondition = new CurrentCondition();
        BaiduSite baiduSite = new BaiduSite();

        //將觀察者註冊到WeatherData
        weatherData.registerObserver(currentCondition);
        //weatherData.registerObserver(baiduSite);

        //測試
        System.out.println("通知各個註冊的觀察者檢視資訊");
        //weatherData.setData(10f,100f,30f);

        weatherData.removeObserver(currentCondition);
        System.out.println();
        System.out.println("通知各個註冊的觀察者, 看看資訊");
        weatherData.setData(100f, 100f, 30.3f);
    }

}

5.以後想新增一個新的網站時,只需要建立一個新的類實現Observer介面,並將新的觀察者註冊到WeatherData中即可。
例如:在百度站點也想實時顯示天氣

package Observer;

/**
 * @title: BaiduSite
 * @Author yzhengy
 * @Date: 2020/7/25 18:55
 * @Question:
 */
public class BaiduSite implements Observer {
    // 溫度,氣壓,溼度
    private float temperature;
    private float pressure;
    private float 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("===百度網站====");
        System.out.println("***百度網站 氣溫 : " + temperature + "***");
        System.out.println("***百度網站 氣壓: " + pressure + "***");
        System.out.println("***百度網站 溼度: " + humidity + "***");
    }
}

相關文章