觀察者模式-發佈訂閱模式-及其實現


題意描述:

觀察者模式和發佈訂閱模式有什麼不同 ? 手寫一個觀察者模式的例子 ?


解題思路:
Alice: 上次講了觀察者模式,發佈訂閱模式是什麼 ?
Bob: 和觀察者模式很類似,發佈訂閱模式其實屬於廣義上的觀察者模式。在觀察者模式中,觀察者需要直接訂閱目標事件。在目標發出內容改變的事件後,直接接收事件並作出響應。而在發佈訂閱模式中,發佈者和訂閱者之間多了一個調度中心。調度中心一方面從發佈者接收事件,另一方面向訂閱者發佈事件,訂閱者需要在調度中心中訂閱事件。通過調度中心實現了發佈者和訂閱者關係的解耦。使用發佈訂閱者模式更利於我們代碼的可維護性。
Alice: 也就是說發佈訂閱模式就是在 觀察者模式的 被觀察者 和 觀察者之間加了一層 調度中心 ?
Bob: 是的,下面的圖解釋的很清楚,就是多了一個調度中心。即實現了 發佈者 和 訂閱者 之間的解耦,還可以在 調度中心加一些細粒度的控制,就是代碼可能會麻煩一點。
在這裏插入圖片描述
在這裏插入圖片描述
Alice: 寫段代碼來遛一遛 😏
Bob: 我來一個發佈訂閱模式吧。

// eventChannel 後是一個 IIEF 立即執行的函數表達式
        var eventChannel = (function () {
            var events = {};
            // 閉包,用於存儲調度中心接收的消息
            return {
                // 訂閱者通過 subscribe 函數訂閱event事件
                subscribe: function (event, handler) {
                    if (!events.hasOwnProperty(event)) {
                        events[event] = [];
                    }
                    events[event].push(handler);
                },

                receiveEvent: function (event) {
                    if (events.hasOwnProperty(event)) {
                        console.log(`非首次接收  ${event}, 嘗試 FireEvent`);
                        this.fireEvent(event, "from receiveEvent");
                    } else {
                        console.log(`首次接收  ${event}`);
                        events[event] = [];
                    }
                },

                // 調度中心選擇 觸發事件處理函數
                fireEvent: function (event, msg) {
                    if (events.hasOwnProperty(event)) {
                        // 有對應的事件處理函數
                        events[event].forEach(handler => {
                            handler(msg);
                            // 調用每個事件處理函數
                        });
                    }
                },

                remove: function (event, handler) {
                    // 移除事件的某一個處理函數
                    if (events.hasOwnProperty(event)) {
                        let index = events[event].indexOf(handler);
                        // 放心,indexOf 使用的是 ===
                        if (index !== -1) {
                            events[event].splice(index, 1);
                        }
                    }
                },

                removeAll: function (event) {
                    // 移除某個事件的所有處理函數
                    if (events.hasOwnProperty(event)) {
                        events[event] = [];
                    }
                }
            }
        })();

        var handler = function (msg) {
            console.log(`handler is running : ${msg}`);
        }

        // publisher 通過 receiveEvent 來發布事件
        eventChannel.receiveEvent('AREUOK');
        eventChannel.subscribe('AREUOK', handler);
        eventChannel.receiveEvent('AREUOK');
        eventChannel.fireEvent('AREUOK', 'Year, I AM OK');
        // 首次接收  AREUOK
        // 非首次接收  AREUOK, 嘗試 FireEvent
        // handler is running: from receiveEvent
        // handler is running: Year, I AM OK

Alice: 你這裏是沒有寫 發佈者 publisher 嗎,還有訂閱者 subscriber 也沒寫 。
Bob: 不過我寫了 調度中心和 二者 交互的函數呀, receiveEvent, subscribe, fireEvent 應該能夠展示 發佈訂閱模式的工作機理了,發佈者和訂閱者只需要調用對應的函數就好了。
Alice: 不錯不錯,我來一個 觀察者模式吧。

       var Observer = function(name){
            // 觀察者的構造函數
            this.name = name;
            this.update = function(msg){
                console.log(`${this.name} get msg: ${msg}`);
            } 
        }

        var Subject = function(name){
            // 被觀察者構造函數
            this.name = name;

            this.observers = [];

            this.addObserver = function(observer){
                this.observers.push(observer);
            }

            this.removeObserver = function(observer){
                let index = this.observers.indexOf(observer);
                if(index !== -1){
                    this.observers.splice(index, 1);
                }
            }

            this.notifyAll = function(){
                this.observers.forEach(observer => {
                    observer.update(this.name + " ~ msg by nofifyAll");
                });
            }
        }

        var ob1 = new Observer('Alice'),
            ob2 = new Observer('Bob');
        
        var sub = new Subject('winter is coming');
        sub.addObserver(ob1);
        sub.addObserver(ob2);
        sub.notifyAll();
        // Alice get msg: winter is coming ~msg by nofifyAll
        // Bob get msg: winter is coming ~msg by nofifyAll

代碼:

  • 在上面

易錯點:

  • 觀察者模式 和 訂閱發佈模式 是有區別的。

總結:

  • 在觀察者模式中,觀察者是知道Subject的,Subject一直保持對觀察者進行記錄。然而,在發佈訂閱模式中,發佈者和訂閱者不知道對方的存在。它們只有通過調度中心進行通信。
  • 在發佈訂閱模式中,組件是鬆散耦合的,正好和觀察者模式相反。
  • 觀察者模式大多數時候是同步的,比如當事件觸發,Subject就會去調用觀察者的方法。而發佈-訂閱模式大多數時候是異步的(使用消息隊列)

參考文獻:


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