JavaScript設計模式之發佈-訂閱模式(觀察者模式)-Part1

《JavaScript設計模式與開發實踐》讀書筆記。javascript

發佈-訂閱模式又叫觀察者模式,它定義了對象之間的一種一對多的依賴關係。當一個對象的狀態發生改變時,全部依賴它的對象都將獲得通知。java

例如:在segmentfault咱們關注了某一個問題,這個時候能夠說是訂閱了這個問題的消息。當該問題有了新的回答、評論的時候,segmentfault系統就會遍歷關注了這個問題的用戶,一次給用戶發消息。算法

如今看看如何一步步實現發佈-訂閱模式。segmentfault

  • 首先,指定好發佈者(如 segmentfault 的 問題系統)設計模式

  • 接着,給發佈者添加一個緩存列表,用於存放回調函數以便通知訂閱者(問題系統的記錄表)緩存

  • 最後,當發佈者發佈消息的時候,會遍歷緩存列表,依次觸發裏面的回調函數(遍歷記錄表,逐個發消息)數據結構

咱們還能夠在回調函數裏面加入一些參數,訂閱者能夠接收這些參數,進行各自的處理。app

var sgQuestionSystem = {};    // 定義segmentfault的問題系統

/* 
 * 緩存列表
 * clientList: {
 *    key: [
 *        id: <int>,        // 惟一標識
 *        fn: null          // 存放回調函數
 *    ]
 * }
 * 
*/
sgQuestionSystem.clientList = {};

/* 
 * 添加訂閱者(訂閱函數),將訂閱的類型與回調函數加入緩存列表
 * key: 消息的類型
 * id: 訂閱的惟一標識
 * fn: 訂閱的回調函數
*/
sgQuestionSystem.listen = function(key, id, fn) {
    if(!this.clientList[key]) {            // 若緩存列表沒有該類型的消息,給該類消息初始化
        this.clientList[key] = []
    }
   
    this.clientList[key].push({            // 將訂閱的id, 回調函數添加到對應的消息列表裏
        id: id,                         
        fn: fn                          
    })
}

// 發佈消息(發佈函數), 依次通知訂閱者
sgQuestionSystem.trigger = function () {
    var key = Array.prototype.shift.call(arguments),    // 取出消息類型
        fns = this.clientList[key];                     // 取出該消息對應的回調函數集合
    
    if(!fns || fns.length == 0) {                      // 若訂閱列表沒有該類型的回到函數,則返回
        return false;                                   
    }
    
    for(var i = 0; i< fns.length; i++) {
         fns[i].fn.apply(this, arguments);               // arguments是發佈消息時附送的參數,去掉了key
    }
}

如今,咱們來進行一些簡單的測試:數據結構和算法

// 張三訂閱問題A
sgQuestionSystem.listen('questionA', 3, function(questionTitle, content) {
    console.log('張三您在早前訂閱了問題:questionA');
    console.log('現' + questionTitle + '有了新動態');
    console.log('內容爲:' + content);
});

// 李四訂閱問題A
sgQuestionSystem.listen('questionB', 4, function(questionTitle, content) {
    console.log('李四您在早前訂閱了問題:questionB');
    console.log('現' + questionTitle + '有了新動態');
    console.log('內容爲:' + content);
})

// 問題系統發佈消息
sgQuestionSystem.trigger('questionA', '問題A', '王五回答了問題A');
sgQuestionSystem.trigger('questionB', '問題B', '吳六回答了問題B');

至此,咱們實現了一個簡單的發佈-訂閱模式,訂閱者能夠訂閱本身感興趣的事件了。函數

各位看官,看累了嗎?

看累的話點一下收藏,以便您看續集。若您仍是精力充沛,那就繼續擼吧。


上部分,咱們實現了一個問題系統的發佈-訂閱模式,如今,咱們要實現一個文章的發佈-訂閱模式,這時候,該怎麼辦?將上面的代碼ctrl + c, ctrl + v, 再改下名字?仍是有更好的解決方案?

答案顯然是有的,咱們能夠將發佈-訂閱功能模塊提取出來,放在一個單獨的對象裏面:

var publishSubscribeEvent = {

    
    /* 
     * 緩存列表
     * clientList: {
     *    key: [
     *        id: <int>,        // 惟一標識
     *        fn: null          // 存放回調函數
     *    ]
     * }
     * 
    */
    clientList: {},
    
    /* 
     * 添加訂閱者(訂閱函數),將訂閱的類型與回調函數加入緩存列表
     * key: 消息的類型
     * id: 訂閱的惟一標識
     * fn: 訂閱的回調函數
    */
    listen: function(key, id, fn) {
        if(!this.clientList[key]) {            
            this.clientList[key] = []
        }
       
        this.clientList[key].push({            
            id: id,                         
            fn: fn                          
        })
    },
    
    // 發佈消息(發佈函數), 依次通知訂閱者
    trigger: function () {
        var key = Array.prototype.shift.call(arguments),
            fns = this.clientList[key];
        
        if(!fns || fns.length == 0) {
            return false;                                   
        }
        
        for(var i = 0; i< fns.length; i++) {
             fns[i].fn.apply(this, arguments);
        }
    }
}

再定義一個安裝發佈-訂閱的函數installPublishSubscribeEvent,這個函數能夠給全部對象都動態安裝發佈-訂閱功能:

var installPublishSubscribeEvent = function(obj) {
    for(var i in publishSubscribeEvent) {
        obj[i] = publishSubscribeEvent[i];
    }
}

再來測試一番,咱們給文章對象 sgArticleSystem 動態添加發布-訂閱功能:

var sgArticleSystem = {};

installPublishSubscribeEvent(sgArticleSystem ); 

// 張三訂閱文章A動態
sgArticleSystem.listen('articleA', 3, function(articleTitle, content) { 
    console.log('張三您在早前訂閱了文章:articleA');
    console.log('現' + articleTitle+ '有了新動態');
    console.log('內容爲:' + content);
});

// 李四訂閱文章B動態
sgArticleSystem.listen('articleB', 4, function(articleTitle, content) { 
    console.log('李四您在早前訂閱了文章:articleB');
    console.log('現' + articleTitle+ '有了新動態');
    console.log('內容爲:' + content);
});

// 文章系統發佈消息
sgArticleSystem.trigger('articleA', 'JavaScript設計模式之發佈-訂閱模式', '做者修改了文章');
sgArticleSystem.trigger('articleB', 'JavaScript設計模式之策略模式', '王五用戶評論了該文章');

好了,該代碼通過自測是沒有什麼問題的,要是各位看官發現問題,歡迎反饋。如今,咱們已經能夠給咱們指定的對象安裝發佈-訂閱模式,可是,是否是還少了點什麼功能呢?

答案就是少了取消訂閱事件的功能。好比張三忽然不想關注該問題的更新動態了,爲了不繼續收到問題系統推送過來的消息,張三須要取消以前訂閱的事件。如今,咱們給 publishSubscribeEvent 對象增長 remove 方法。

publishSubscribeEvent.remove = function(key, id) {
    var fns = this.clientList[key];
    
    if(!fns) {                // 若是key對應的消息沒人訂閱,直接返回
        return false;
    }
    
    if(!id) {                // 若是沒傳具體的惟一標識,則取消key的全部對應消息
        fns && (fns.length = 0);
    } else {
        for(var l = fns.length - 1; l >=0; l--) {
            var _id = fns[l].id;
            if(_id == id) {
                fns.splice(l, 1);    // 刪除訂閱者的回調函數
            }
        }
    }
}
// 測試代碼
var sgArticleSystem = {};

installPublishSubscribeEvent(sgArticleSystem ); 

// 張三的訂閱
sgArticleSystem.listen('articleA', 3, function(articleTitle, content) { 
    console.log('張三您在早前訂閱了文章:articleA');
    console.log('現' + articleTitle+ '有了新動態');
    console.log('內容爲:' + content);
});

// 李四的訂閱
sgArticleSystem.listen('articleA', 4, function(articleTitle, content) { 
    console.log('李四您在早前訂閱了文章:articleA');
    console.log('現' + articleTitle+ '有了新動態');
    console.log('內容爲:' + content);
});

sgArticleSystem.remove('articleA', 3);    // 刪除張三的訂閱
sgArticleSystem.trigger('articleA', 'JavaScript設計模式之發佈-訂閱模式', '做者修改了文章');

上面的代碼跟原著有所不一樣,原著是在刪除訂閱的時候是用對比回調函數的,而我是往緩存列表加了一個惟一的標識,用於識別。

至此,咱們的發佈-訂閱模式第一部分已完結,歡迎你們收藏評論。

附:
JavaScript設計模式之發佈-訂閱模式(觀察者模式)-Part2

JavaScript數據結構和算法系列:
JS 棧
JS 隊列-優先隊列、循環隊列

JavaScript設計模式系列:
JavaScript設計模式之策略模式

相關文章
相關標籤/搜索