jdk觀察者模式的黑暗面

什麼是觀察者模式

觀察者模式(Observer Pattern)定義了對象之間的一對多依賴,讓多個觀察者對象同時監聽一個主體對象,當主體對象發生變化時,它的所有依賴者(觀察者)都會收到通 知並更新,屬於行爲型模式。觀察者模式有時也叫做發佈訂閱模式,主要用 於在關聯行爲之間建立一套觸發機制的場景

觀察者模式示例

假如我們需要從氣象臺獲取天氣信息用作展示,當天氣信息發生改變之後,我們也需要同步發生改變。
1、建立一個Subject接口

package com.zwx.design.pattern.observe.headfirst;

public interface Subject {
    void registerObserver(Observer o);//註冊觀察對象
    void removeObserver(Observer o);//移除觀察對象
    void notifyObservers();//通知觀察對象
}

2、建立一個WeatherData(被觀察者)實現Subject接口(面向接口編程會更易於擴展,這裏也可以不用Subject接口,直接建立WeatherData實現)

package com.zwx.design.pattern.observe.headfirst;

import java.util.ArrayList;
import java.util.List;

public class WeatherData implements Subject {
    private List<Observer> observers;

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

    public void setMessurements(float temperature,float humidity,float pressure){
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;

        notifyObservers();//氣溫信息發生變化時,通知所有觀察者
    }

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

    /**
     * 註冊觀察者
     * @param o
     */
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    /**
     * 移除觀察者
     * @param o
     */
    @Override
    public void removeObserver(Observer o) {
        int i = observers.indexOf(o);
        if(i >= 0){
            observers.remove(i);
        }

    }

    /**
     * 通知所有觀察者
     */
    @Override
    public void notifyObservers() {
        for (Observer observer : observers){
            observer.update(temperature,humidity,pressure);
        }
    }
}

3、建立一個***觀察者***接口

package com.zwx.design.pattern.observe.headfirst;

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

這個接口只定義了一個update方法,用於更新氣象數據
4、建立一個WeatherDisplay(觀察者)類實現Observer接口

package com.zwx.design.pattern.observe.headfirst;

public class WeatherDisplay implements Observer {
    private Subject subject;
    private float temperature;//溫度
    private float humidity;//溼度
    private float pressure;//氣壓

    public WeatherDisplay(Subject subject) {//註冊監聽對象
        this.subject = subject;
        subject.registerObserver(this);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
    }

    public void display(){
        System.out.println("當前最新的溫度爲:" + temperature + ",溼度爲:" + humidity +
                ",氣壓爲:" + pressure);
    }
}

5、最後再寫一個測試類測試:

package com.zwx.design.pattern.observe.headfirst;

public class WeatherDataTest {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();//氣象數據即被觀察者
        WeatherDisplay weatherDisplay = new WeatherDisplay(weatherData);//天氣展示即觀察者
        weatherData.setMessurements(37.2f,80f,32.5f);
        weatherDisplay.display();//天氣展示
    }
}

最後輸出結果爲:
在這裏插入圖片描述
說明被觀察者WeatherData氣象發生變化時,觀察者WeatherDisplay也及時獲得了最新的數據變化,從而實現了一個簡單的觀察者模式!

上面的示例中存在一種問題,那就是數據是被push(推)過來的,也就是說不管觀察者想不想要,只要氣象發生變化了,被觀察者就會把數據push(推)給觀察者,那麼能不能實現讓觀察者主動去pull(拉)呢?答案是肯定的,上面我們只要把WeatherData中每個數據都提供getter方法,然後再稍作改進就可以實現!
JDK中自帶實現了觀察者模式,並且實現了push(推)和pull(拉)兩種類型,接下來就讓我們用JDK自帶的觀察者模式來實現pull(拉)的場景。

JDK觀察者模式使用

假設我們在空間中發了一條動態,需要把這條動態推送給好友
1、首先建立一個Zone類(被觀察者)實現Observable類

package com.zwx.design.pattern.observe.trendsNotice;

import java.util.Observable;

public class Zone extends Observable {
    public void publishTrends(Trends trends){
        System.out.println(trends.getNickName() + "發表了一個動態【" + trends.getContent() + "】" );
        setChanged();//僅作標識用
        notifyObservers(trends);//通知所有觀察者
    }
}

注意這裏的Observable是java.util包下面的,這個類裏面定義了一個發表動態的方法。這裏setChanged()僅僅只是把一個改變標識設置爲true
在這裏插入圖片描述
之所以要這一個,是因爲JDK中的notifyObservers方法中會判斷這個標識是否爲true,爲true纔會進行通知
在這裏插入圖片描述
2、定義一個動態類

package com.zwx.design.pattern.observe.trendsNotice;

public class Trends {
    private String nickName;//發表動態的用戶暱稱
    private String content;//發表的動態內容

    public String getNickName() {
        return nickName;
    }

    public void setNickName(String nickName) {
        this.nickName = nickName;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

3、定義一個Friends類(觀察者),實現Observer接口

package com.zwx.design.pattern.observe.trendsNotice;

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

public class Friends implements Observer {
    private String friendName;

    public String getFriendName() {
        return friendName;
    }

    public void setFriendName(String friendName) {
        this.friendName = friendName;
    }

    @Override
    public void update(Observable o, Object arg) {
     	Trends trends = new Trends();
        if(null != arg && arg instanceof Trends){
            trends = (Trends)arg;
        }
        System.out.println(this.getFriendName() + ",您好!您收到了來自" + trends.getNickName() +
                "的一條動態【" + trends.getContent() + "】" + "快去點贊吧!");

    }
}

注意這裏的update方法中的第2個參數,如果上面Zone類中的方法publishTrends方法中調用的notifyObservers方法不傳入參數trends,這裏拿到的就是空,也就是相當於實現了push(推),傳入了參數我們update中就能拿到數據對象,這時候我們就可以主動去pull(拉)數據,只選擇自己想要的數據進行展示或者處理
4、最後寫一個測試類測試

package com.zwx.design.pattern.observe.trendsNotice;

public class ObserverTest {
    public static void main(String[] args) {
        Zone zone = new Zone();
        Friends friends = new Friends();
        friends.setFriendName("張三丰");

        Trends trends = new Trends();
        trends.setNickName("張無忌");
        trends.setContent("祝太師傅長命百歲!");
        zone.addObserver(friends);
        zone.publishTrends(trends);
    }
}

輸出結果爲如下:
在這裏插入圖片描述
說明發表動態之後朋友收到了通知,且拿到了動態相關數據,實現了觀察者模式pull(拉)數據

java.util.Observable的黑暗面

1、Observable是一個類而不是一個接口,所以就限制了它的使用和服用,如果某類同時想具有Observable類和另一個超類的行爲,就會有問題,畢竟java不支持多繼承。
2、Observable將關鍵的方法保護起來了,比如setChanged()方法,這意味着除非我們繼承自Observable,否則無法創建Observable實例並組合到我們自己的對象中來,這個設計違反了設計原則:多用組合,少用繼承。

觀察者模式的有優點

1、觀察者和被觀察者之間建立了一個抽象的耦合。
2、觀察者模式支持廣播通信

觀察者模式的有缺點

1、觀察者之間有過多的細節依賴、提高時間消耗及程序的複雜度。 2、使用要得當,要避免循環調用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章