【設計模式】漢堡中的設計模式——觀察者模式

碼農Amg發表於2021-11-16

【設計模式】漢堡中的設計模式——觀察者模式

情景帶入

對於愛吃麥當勞的我來說,自然是不想錯過各種動態,可是我又不可能【無時無刻】的蹲在店裡等新品吧(還是要搬磚的)

那麼有沒有一種好的方法,在麥當勞【推出新品或釋出動態】的時候,我能及時收到通知?

或許有人會說,你把麥當勞內部員工要一下微信不久可以了,這樣一有新品,就讓他來通知一下你就好啦!

emm理想確實是很豐滿的,但是現實卻是老子哪裡會搭訕....

那難道就沒有辦法了嗎?

怎麼可能,你關注下公眾號不久可以了???

image

為什麼關注公眾號就可以

我們都知道,關注指定公眾號之後,只要該公眾號釋出動態的時候,你就可以在【訂閱號訊息】中看到新動態,正如下圖所示

image

公眾號是基於釋出-訂閱模式,瞭解這個跟我們今天要講的觀察者模式有什麼關聯嗎?

釋出者-訂閱者模式與觀察者模式

觀察者模式(Observer)

雖然觀察者模式也被稱為“釋出-訂閱”模式,但是我認為這個釋出-訂閱跟接下來要講的釋出者-訂閱者模式是不同

觀察者模式的定義是:當物件存在一對多關係的時候,一個物件修改了自身,則會自動通知依賴他的物件

帶入我們的例子中,就是有很多個吃貨(Observer)“觀察著” 麥當勞公眾號(Subject)

Observer、Subject是觀察者模式的抽象描述,而他的類圖類似是如此的,其中Observer是個抽象類或者介面,底下就是真正的觀察者,Subject在這裡沒有設計成抽象的,你也可以讓Subject抽象起來,再給出具體的實現類

image

現在對照著類圖,再看一下觀察者模式的定義,多個觀察者把自己註冊到Subject中,當Subject發生變動,便會觸發通知方法,而通知方法就會呼叫各個具體觀察者裡面的具體實現

這便是觀察者模式基本的概念,那麼觀察者模式的好處究竟是什麼?

  1. 我們可以知道,Subject(被觀察者)和Observer(觀察者)是抽象耦合的,也即你觀察者怎麼變化都不會影響到Subject,如果要新增觀察者,也只需要新建一個類,然後註冊到Subject裡面即可
  2. 擁有觸發機制,一個物件更改,關聯物件就可以收到通知,甚是方便

那麼觀察者的缺點又是什麼?

  1. 細心觀察定義,Subject裡面可能會有多個觀察者,那麼這個多個,究竟可能會有多少個?不確定,如果有3億的人關注了麥當勞公眾號,那麼釋出動態的時候,就會通知到3億人,會耗費很多時間
  2. 這種真的就是“被通知”,也即觀察者不知道中間經歷了什麼,而只是知道被觀察的物件發生了變化(類似一個女生突然找你吃飯,你還覺得很高興,殊不知也許她剛剛被放飛機了,所以才找你 ,你不知道箇中緣由,當然這也未必是壞事:),因為這樣你就可以確定,我不需要知道你怎麼做,而只需要這麼做,我便能被通知到,觀看的角度不一樣,就會有不同的想法 )

釋出-訂閱模式(Publisher-Subscriber)

其實發布-訂閱模式跟觀察者模式之間最大的區別就是Publisher和Subscriber間,還存在著一箇中介軟體來負責排程,釋出者不需要知道訂閱者是誰,反過來也是一樣的

這麼一說就有點像Kafka裡面的producer-topic-consumer了,簡單畫個圖

image

當然實際上,消費者從屬於消費者群組,一個群組裡面的消費者訂閱的是同一個主題,每個消費者接收主題一部分分割槽的訊息,也即對應裡面具體的partition,這裡就不額外展開了

正如上圖,producer和consumer是不認識的,但是他們都認識一個傢伙,那就是topic,producer充當publisher的角色,把資訊傳送到topic,消費者監聽(訂閱)topic,一旦這個topic接收到資訊,就會把資訊推送給監聽的消費者

觀察者模式主要是以同步的方式觸發機制,而釋出-訂閱更多的是用在非同步的情況下,藉助類似Kafka的訊息中介軟體完成元件間的鬆散耦合

所以其他兩者並不等價,只能說有那麼幾分相似

觀察者模式的落地實現

  1. 首先按照想法,我們得有一個McDonalds的Subject,假設這就是【麥當勞官方公眾號】,裡面有動態釋出的方法、註冊/移除觀察者的方法、以及一鍵訊息提醒的方法

    /**
     * @Author: Amg
     * @Date: Created in 23:04 2021/11/15
     * @Description: 麥當勞公眾號
     */
    public class McDonalds {
    
        //觀察者的集合,需要這裡這裡泛型接收的是觀察者的頂層類
        private List<Observer> list = new ArrayList<>();
        //接收新動態
        public String status;
    
        public McDonalds(String status) {
            this.status = status;
        }
    
        /**
         * 新增觀察者
         * @param observer
         */
        public void registerObserver(Observer observer) {
            list.add(observer);
        }
    
        /**
         * 移除觀察者
         * @param observer
         */
        public void removeObserver(Observer observer) {
            list.remove(observer);
        }
    
        /**
         * 遍歷集合,取出每一個觀察者,然後呼叫他們的update方法
         */
        public void notifyAllObserver() {
            System.out.println("[麥當勞]:" + status);
            for (Observer observer : list) {
                observer.update();
            }
        }
    
    }
    
    
  2. 然後還得有觀察者物件,觀察者可能會有多個,所以我們直接建一個頂層抽象類,然後給出一個【吃貨物件】

    /**
     * @Author: Amg
     * @Date: Created in 23:04 2021/11/15
     * @Description: TODO
     */
    public abstract class Observer {
        //名字
        protected String name;
        //話語
        protected String say;
    
        public Observer(String name,String say) {
            this.name = name;
            this.say = say;
        }
    
        abstract void update();
    }
    
    
    /**
     * @Author: Amg
     * @Date: Created in 23:10 2021/11/15
     * @Description: 吃貨物件
     */
    public class FoodieFan extends Observer {
    
        public FoodieFan(String name, String say) {
            super(name,say);
        }
    
        @Override
        void update() {
            System.out.println(String.format("[%s: %s]",name,say));
        }
    }
    
  3. 當然,最好需要有一個客戶端給展示起來

    /**
     * @Author: Amg
     * @Date: Created in 23:08 2021/11/15
     * @Description: 客戶端
     */
    public class Client {
    
        public static void main(String[] args) {
    
            McDonalds mcDonalds = new McDonalds("[麥當勞新品推薦:敷面膜的安格斯,當前僅需41.5,趕快來品嚐吧!]");
    
            FoodieFan foodieA = new FoodieFan("吃貨Amg","這個漢堡可不能錯過!");
            FoodieFan foodieB = new FoodieFan("吃貨胖頭魚","emm這個我倒是沒什麼興趣,有新品雪糕再通知我好了");
            FoodieFan foodieC = new FoodieFan("吃貨大頭蝦","看來今晚的宵夜有著落了,這就去盤他");
    
            mcDonalds.registerObserver(foodieA);
            mcDonalds.registerObserver(foodieB);
            mcDonalds.registerObserver(foodieC);
    
            mcDonalds.notifyAllObserver();
        }
    }
    
    //最終輸出的結果
    
    [麥當勞]:[麥當勞新品推薦:敷面膜的安格斯,當前僅需41.5,趕快來品嚐吧!]
    [吃貨Amg: 這個漢堡可不能錯過!]
    [吃貨胖頭魚: emm這個我倒是沒什麼興趣,有新品雪糕再通知我好了]
    [吃貨大頭蝦: 看來今晚的宵夜有著落了,這就去盤他]
    

只要思想不滑坡,程式碼還不是手到擒來,所以理解思想才是最關鍵的

總結

再總結一下觀察者麵包與釋出者-訂閱者模式之間的區別

觀察者模式 釋出-訂閱模式
Subject和Observer之間是直接溝通的 Publisher和Subscriber是不直接溝通,通過中介軟體來交流
觀察者模式適用於單體應用程式 釋出-訂閱模式適用於跨應用的程式
觀察者主要以同步方式實現 釋出-訂閱則主要以非同步的方式實現

最後來一波王婆賣瓜,更多精彩盡在微信公眾號【碼農Amg】,這裡將不定期的更新日常工作總結和學習中的重難點知識分享,趕快來訂閱我吧!
image

相關文章