設(shè)計模式之觀察者模式

定義

定義對象間的一種一對多的依賴關(guān)系,當(dāng)一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都得到通知并被自動更新。

觀察者模式的UML類圖及說明

image.png

如上圖(圖片來源于《head_first設(shè)計模式》)所示,觀察者的uml中主要有以下類
1.主題Subject(接口)
Subject 對象帶有綁定觀察者到 Client 對象和從 Client 對象解綁觀察者的方法,主要為觀察者注冊方法(registerObserver),觀察者刪除方法(removeObserver),通知觀察者方法(notifyObservers)
2.觀察者Observer(接口)
Observer觀察者一般是一個接口,每一個實現(xiàn)該接口的實現(xiàn)類都是具體觀察者。主要提供一個update方法。
3.具體主題ConcreteSubject(Subject的具體實現(xiàn))
是對主題Subject接口的具體實現(xiàn),該類可根據(jù)具體業(yè)務(wù)擴展
4.具體觀察者ConcreteObserver(Observer的具體實現(xiàn))
是對Observer觀察者的具體實現(xiàn),一般訂閱者會有多個,所以該類可通過自己需要的消息進行擴展

觀察者模式的優(yōu)缺點及應(yīng)用場景

優(yōu)點:
1.觀察者和被觀察者是抽象耦合的。
2.建立一套觸發(fā)機制。
缺點:
1.如果一個被觀察者對象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間。
2.如果在觀察者和觀察目標(biāo)之間有循環(huán)依賴的話,觀察目標(biāo)會觸發(fā)它們之間進行循環(huán)調(diào)用,可能導(dǎo)致系統(tǒng)崩潰。
3.觀察者模式?jīng)]有相應(yīng)的機制讓觀察者知道所觀察的目標(biāo)對象是怎么發(fā)生變化的,而僅僅只是知道觀察目標(biāo)發(fā)生了變化。
應(yīng)用場景:
1.關(guān)聯(lián)行為場景
2.事件多級觸發(fā)場景
4.跨系統(tǒng)的消息變換場景,如消息隊列的處理機制
應(yīng)用實例:
1.新聞的發(fā)布訂閱
2.天氣信息的發(fā)布訂閱

注意事項:
1、JAVA 中已經(jīng)有了對觀察者模式的支持類。
2、避免循環(huán)引用。
3、如果順序執(zhí)行,某一觀察者錯誤會導(dǎo)致系統(tǒng)卡殼,一般采用異步方式。

Spring中的觀察者模式

1.事件(ApplicationEvent)
ApplicationEvent 是所有事件對象的父類。ApplicationEvent 繼承自 jdk 的 EventObject, 所有的事件都需要繼承 ApplicationEvent, 并且通過source得到事件源。

2.事件監(jiān)聽(ApplicationListener)
ApplicationListener 事件監(jiān)聽器,也就是觀察者。繼承自 jdk 的 EventListener,該類中只有一個方法 onApplicationEvent。當(dāng)監(jiān)聽的事件發(fā)生后該方法會被執(zhí)行。

3.事件發(fā)布(ApplicationContext)
ApplicationContext 是 Spring 中的核心容器,在事件監(jiān)聽中 ApplicationContext 可以作為事件的發(fā)布者,也就是事件源。因為 ApplicationContext 繼承自 ApplicationEventPublisher。在 ApplicationEventPublisher 中定義了事件發(fā)布的方法 — publishEvent(Object event)

4.事件管理(ApplicationEventMulticaster)
ApplicationEventMulticaster 用于事件監(jiān)聽器的注冊和事件的廣播。監(jiān)聽器的注冊就是通過它來實現(xiàn)的,它的作用是把 Applicationcontext 發(fā)布的 Event 廣播給它的監(jiān)聽器列表。

觀察者模式的實現(xiàn)

現(xiàn)在的線上直播模式非常的火,在這里,以簡單的LOL比賽信息訂閱為例。比如我們要關(guān)注今天有哪些了LPL的比賽信息,后幾天有哪些比賽信息,我們就可以點擊訂閱,訂閱后,有新的比賽信息,就會推送給你。

這樣的場景下,我們就可以用觀察者模式來實現(xiàn):

定義主題接口

/**
 * 主題接口,對象通過此接口注冊為觀察者,或者把自己從觀察者中刪除
 *
 * @author yyl
 */
public interface Subject {

    /**
     * 觀察者注冊
     *
     */
    void registerObserver(Observer observer);

    /**
     * 刪除觀察者
     *
     */
    void removeObserver(Observer observer);

    /**
     * 通知觀察者
     *
     */
    void notifyObservers();
}

定義主題接口的具體實現(xiàn),及LOL比賽信息主題

/**
 * lol主題
 *
 * @author yyl
 */
public class LolSubject implements Subject {

    /** 今日比賽信息*/
    private String msg;

    /** 后幾日比賽信息*/
    private List<String> msgList;

    // 用戶列表
    private static List<Observer> userList = new ArrayList<>();

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

    @Override
    public void removeObserver(Observer observer) {
        userList.remove(observer);
    }

    @Override
    public void notifyObservers() {
        userList.forEach(observer -> observer.update());
    }

    /**
     * 發(fā)布信息,并通知觀察者
     *
     * @author yyl
     */
    public void setMsgs(String msg,List<String> msgList){
        // 更改通知信息
        this.msg = msg;
        this.msgList = msgList;
        // 通知觀察者
        notifyObservers();
    }

    public String getMsg() {
        return msg;
    }

    public List<String> getMsgList() {
        return msgList;
    }
}

定義觀察者Observer

/**
 * 觀察者Observer,觀察者一般是一個接口,每一個實現(xiàn)該接口的實現(xiàn)類都是具體觀察者
 *
 * @author yyl
 */
public interface Observer {
    /**
     * 接受消息后的具體邏輯處理
     */
    void update();
}

具體觀察者,今日比賽信息觀察者

/**
 * 具體觀察者,今日比賽信息觀察者
 *
 * @author yyl
 */
public class CurrentObserver implements Observer {

    /**lol比賽信息主題 */
    private LolSubject subject;

    /**訂閱的信息 */
    private String msg;

    public CurrentObserver(LolSubject subject) {
        this.subject = subject;
        // 觀察者注冊
        this.subject.registerObserver(this);
    }

    @Override
    public void update() {
        this.msg = this.subject.getMsg();
        System.out.println("今日比賽:" + this.msg);
    }
}

具體觀察者,后幾日比賽信息觀察者

/**
 * 具體觀察者,后幾日比賽信息觀察者
 *
 * @author yyl
 */
public class FutureObserver implements Observer {

    /**lol比賽信息主題 */
    private LolSubject subject;

    /**訂閱的信息 */
    private List<String> msgList;

    public FutureObserver(LolSubject subject) {
        this.subject = subject;
        // 觀察者注冊
        this.subject.registerObserver(this);
    }

    @Override
    public void update() {
        this.msgList = this.subject.getMsgList();
        System.out.println("比賽預(yù)告:");
        msgList.forEach(s -> System.out.println(s));
    }
}

通知者進行通知 Client

public class TestObserver {

    public static void main(String[] args) {

        // 主題
        LolSubject lolSubject = new LolSubject();
        // 觀察者
        CurrentObserver currentObserver = new CurrentObserver(lolSubject);
        FutureObserver futureObserver = new FutureObserver(lolSubject);

        List<String> msgList = new ArrayList<>();
        msgList.add("2020-06-22 RNG VS IG");
        msgList.add("2020-06-22 VG VS LGD");
        msgList.add("2020-06-22 V5 VS OMG");
        // 發(fā)布信息
        lolSubject.setMsgs("TSE VS FPX", msgList);
    }

}

如上代碼,有兩個觀察者,在發(fā)布信息的比賽信息后,觀察者就可以獲取自己關(guān)注的信息,然后進行自己的業(yè)務(wù)邏輯處理。代碼輸出如下


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容