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

定義

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

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

image.png

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

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

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

注意事項(xiàng):
1九孩、JAVA 中已經(jīng)有了對(duì)觀察者模式的支持類先馆。
2、避免循環(huán)引用躺彬。
3煤墙、如果順序執(zhí)行,某一觀察者錯(cuò)誤會(huì)導(dǎo)致系統(tǒng)卡殼宪拥,一般采用異步方式仿野。

Spring中的觀察者模式

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

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

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

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

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

現(xiàn)在的線上直播模式非常的火,在這里稀蟋,以簡單的LOL比賽信息訂閱為例煌张。比如我們要關(guān)注今天有哪些了LPL的比賽信息,后幾天有哪些比賽信息退客,我們就可以點(diǎn)擊訂閱骏融,訂閱后,有新的比賽信息萌狂,就會(huì)推送給你档玻。

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

定義主題接口

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

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

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

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

定義主題接口的具體實(shí)現(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务傲,觀察者一般是一個(gè)接口凉当,每一個(gè)實(shí)現(xiàn)該接口的實(shí)現(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;
        // 觀察者注冊(cè)
        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;
        // 觀察者注冊(cè)
        this.subject.registerObserver(this);
    }

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

通知者進(jìn)行通知 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);
    }

}

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


image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瓜贾,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子携悯,更是在濱河造成了極大的恐慌祭芦,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,029評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件憔鬼,死亡現(xiàn)場(chǎng)離奇詭異龟劲,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)轴或,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,395評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門昌跌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人照雁,你說我怎么就攤上這事蚕愤。” “怎么了饺蚊?”我有些...
    開封第一講書人閱讀 157,570評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵萍诱,是天一觀的道長。 經(jīng)常有香客問我污呼,道長裕坊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,535評(píng)論 1 284
  • 正文 為了忘掉前任曙求,我火速辦了婚禮碍庵,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘悟狱。我一直安慰自己静浴,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,650評(píng)論 6 386
  • 文/花漫 我一把揭開白布挤渐。 她就那樣靜靜地躺著苹享,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上得问,一...
    開封第一講書人閱讀 49,850評(píng)論 1 290
  • 那天囤攀,我揣著相機(jī)與錄音,去河邊找鬼宫纬。 笑死焚挠,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的漓骚。 我是一名探鬼主播蝌衔,決...
    沈念sama閱讀 39,006評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蝌蹂!你這毒婦竟也來了噩斟?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,747評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤孤个,失蹤者是張志新(化名)和其女友劉穎剃允,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體齐鲤,經(jīng)...
    沈念sama閱讀 44,207評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡斥废,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,536評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了佳遂。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片营袜。...
    茶點(diǎn)故事閱讀 38,683評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丑罪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情凤壁,我是刑警寧澤吩屹,帶...
    沈念sama閱讀 34,342評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站拧抖,受9級(jí)特大地震影響煤搜,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜唧席,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,964評(píng)論 3 315
  • 文/蒙蒙 一擦盾、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧淌哟,春花似錦迹卢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,772評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至掉弛,卻和暖如春症见,著一層夾襖步出監(jiān)牢的瞬間喂走,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,004評(píng)論 1 266
  • 我被黑心中介騙來泰國打工谋作, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留芋肠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,401評(píng)論 2 360
  • 正文 我出身青樓遵蚜,卻偏偏與公主長得像帖池,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子谬晕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,566評(píng)論 2 349