JavaScript 觀察者模式

觀察者模式又叫做發布-訂閱模式。這是一種一對多的對象依賴關系,當被依賴的對象的狀態發生改變時,所有依賴于它的對象都將得到通知。

生活中的觀察者模式

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

觀察者模式的優勢

發布和訂閱這兩個對象是松耦合地聯系在一起的,它們不用彼此熟悉內部的實現細節,但這不影響它們之間的通信,它們只要知道彼此需要做什么就行。當有新訂閱者增加時,發布者不需要任何更改,同樣的當發布者改變時,訂閱者也不會受到影響。

就像新聞聯播一樣里面的央視主持人換了,也不影響我們看央視的新聞聯播,同樣你看或不看新聞聯播,對央視來說也無影響。

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

JavaScript中的觀察者模式

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

DOM事件

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

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

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

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

訂閱者可以任意的添加,發布者也可以隨意的修改。

自定義事件

雖然,使用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調到登錄模式,就馬上收在離線模式期間接收到的信息。

這樣的例子在生活中也有很多,還拿天氣預報,它也可以理解為是先發布,我們后訂閱的模式。天預報信息會發布在網上,存儲在各個服務器上,我們需要時打開手機就可以得到。

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

設計模式周周講

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 1 場景問題# 1.1 訂閱報紙的過程## 來考慮實際生活中訂閱報紙的過程,這里簡單總結了一下,訂閱報紙的基本流程...
    七寸知架構閱讀 4,648評論 5 57
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,869評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,076評論 25 708
  • 如果有人傾聽你,不對你評頭論足,不替你擔驚受怕,也不想改變你,這多美好啊! (這段文字里包含了當我們傾聽時,容易出...
    素樸之行閱讀 195評論 0 0
  • 【小斯】的情書 小丫頭, 回到家里就看到你在沙發邊徘徊,還嘴里念念有詞,雖然不是很清楚,但是仔細聽還是依稀聽...
    浮沉浮沉閱讀 116評論 0 0