JavaScript 觀察者模式

觀察者模式又叫作發佈-訂閱模式。這是一種一對多的對象依賴關係,當被依賴的對象的狀態發生改變時,全部依賴於它的對象都將獲得通知。git

生活中的觀察者模式

就如咱們在專賣店預約商品(如:蘋果手機),咱們會向專賣店提交預約申請,而後店家受申請,正常這樣就完事了。假如,近段時間蘋果手機的需求很大,而商品有限,那麼商家就會要這些果粉預留電話等待通知,等到手機一到,商家就會遍歷果粉預留信息,而後發通知給這些果粉。生活中商家強調客戶在家等通知便可,而且說一有消息就會通知客戶,而不會傻到要客戶主動打電話詢問,這樣不只客戶的代價比較大,商家的負荷更大,用戶的輪詢方式也從打電話變成了查看短信息。github

觀察者模式的優點

發佈和訂閱這兩個對象是鬆耦合地聯繫在一塊兒的,它們不用彼此熟悉內部的實現細節,但這不影響它們之間的通訊,它們只要知道彼此須要作什麼就行。當有新訂閱者增長時,發佈者不須要任何更改,一樣的當發佈者改變時,訂閱者也不會受到影響。設計模式

就像新聞聯播同樣裏面的央視主持人換了,也不影響咱們看央視的新聞聯播,一樣你看或不看新聞聯播,對央視來講也無影響。數組

在異步通訊中觀察者模式也是大有好處,發佈者只需按順序的發佈事件便可,而訂閱者只需在異步運行期間訂閱相關事件便可。緩存

JavaScript中的觀察者模式

在JavaScript中觀察者模式的實現主要用事件模型。服務器

DOM事件

document.body.addEventListener('click', function() {
    console.log('hello world!');
});

相信這樣的代碼很多的同窗都寫過,但我要說這其實就是一種觀察者模式的實現,可能一些童鞋還不信,那麼看一看修改後的代碼。app

// 發佈者
var pub = function() {
    console.log('歡迎訂閱!')
}
// 訂閱者
var sub = document.body;

// 訂閱者實現訂閱
sub.addEventListener('click', pub, false);

訂閱者能夠任意的添加,發佈者也能夠隨意的修改。dom

自定義事件

雖然,使用dom事件能夠輕鬆解決咱們開發中的一部分問題;可是還有一些問題須要咱們使用自定義事件來完成。
那面就說一說如何用自定義事件實現代理。異步

咱們還以預約手機爲例,參考dom事件的原理來實現觀察者模式,用用戶的電話號碼做爲類型,用戶的定購信息用一個回調函數來表示。函數

基本概念定義以下:

  • 商家: 發佈者
  • 客戶: 訂閱者
  • 緩存列表:記錄客戶的電話,方便商家遍歷發通知消息給客戶

注:緩存列表,我將它定義爲一個對象,用戶的電話號碼做爲key,用戶的預約信息是個數組做爲value。

代碼實現以下:

// 定義商家
var merchants = {};
// 定義預約列表
merchants.orderList = {};
// 將增長的預訂者添加到預約客戶列表中
merchants.listen = function(id, info) {
    if(!this.orderList[id]) {
        this.orderList[id] = [];
    }
    this.orderList[id].push(info);
    console.log('預約成功')
};
//發佈消息
merchants.publish = function() {
    var id = Array.prototype.shift.call(arguments);
    var infos = this.orderList[id];
    // 判斷是否有預訂信息
    if(!infos || infos.length === 0) {
        console.log('您尚未預訂信息!');
        return false;
    }
    // 若是有預訂信息,則循環打印
    for (var i = 0, info; info = infos[i++];) {
        console.log('尊敬的客戶:');
        info.apply(this, arguments);
        console.log('已經到貨了');
    }
};
// 定義一個預訂者customerA,並指定預約信息
var customerA = function() {
    console.log('黑色至尊版一臺');
};
// customerA 預約手機,並留下預定電話
merchants.listen('15888888888', customerA); // 預約成功
// 商家發佈通知信息
merchants.publish('15888888888');
/**
   尊敬的客戶:
   黑色至尊版一臺
   已經到貨了
 */

取消訂閱

固然,現實中咱們能夠預約,那麼也能夠取消預約。其實取消預約的方式也比較簡單,就是將客戶從預約列表中清除出去。代碼實現以下:

merchants.remove = function(id, fn) {
    var infos = this.orderList[id];

    if(!infos) return false;

    if(!fn) {
        infos && (infos.length = 0);
    } else {
        for(var i = 0, len = infos.length; i < len; i++) {
            if(infos[i] === fn) {
                infos.splice(i, 1);
            }
        }
    }
};
merchants.remove('15888888888', customerA);
merchants.publish('15888888888'); // 您尚未預訂信息!

全局的觀察者模式

實現的代碼結構以下:

var observer = (function() {
    var orderList = {},
        listen,
        publish,
        remove;
    listen = function(id, fn) {
        ...
    };

    publish = function() {
        ...
    };

    remove = function(id, fn) {
        ...
    };

    return {
        listen: listen,
        publish: publish,
        remove: remove
    }
})();

優勢:

使用了全局的觀察者模式後,咱們不用管商家是誰,只要他能提供咱們所須要的東西便可;並且咱們也避免了爲不一樣的商家都建立listen,publish,remove方法,這樣能夠減小資源的浪費。

缺點:

使用全局的觀察者模式會明顯下降對象之間的聯繫。一些方法將會被隱藏,而有時咱們偏偏須要這些方法的暴露。

是先訂閱,仍是先發布

在我被問到這個問題時,我也是一愣,當時腦殼裏就冒出了‘你怎麼不問是先有雞,仍是先有蛋’這樣的想法。

按照個人理解咱們實現觀察者模式,都是訂閱者先訂閱,而後接收發布者的通知消息。沒有反過來想,發佈者先發布一條消息,而後等訂閱者接收,由於在個人想象中,若是沒有訂閱者,這消息怎麼成功發佈。

後來有人跟我說有這樣的業務實現,當時我就不假思索的問什麼業務,他說QQ的離線模式。這種先發布後訂閱的形式是將信息先存儲起來,等到訂閱者訂閱,就當即將信息發送給訂閱者。如:當咱們將QQ調到離線模式,咱們就沒法接收信息;當咱們將QQ調到登陸模式,就立刻收在離線模式期間接收到的信息。

這樣的例子在生活中也有不少,還拿天氣預報,它也能夠理解爲是先發布,咱們後訂閱的模式。天預報信息會發布在網上,存儲在各個服務器上,咱們須要時打開手機就能夠獲得。

注:提到觀察者模式咱們就不得不說一下推模型和拉模型。推模型在事件發生時,發佈者會將變化狀態和數據都推送給訂閱者;拉模型在事件發生時,發佈者只會給訂閱者一個狀態改變通知,訂閱者會根據發佈者提供的接口主動拉取數據。

設計模式週週講

相關文章
相關標籤/搜索