JavaScript 觀察者模式

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

生活中的觀察者模式

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

觀察者模式的優(yōu)勢

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

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

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

JavaScript中的觀察者模式

在JavaScript中觀察者模式的實現(xiàn)主要用事件模型。

DOM事件

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

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

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

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

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

自定義事件

雖然,使用dom事件可以輕松解決我們開發(fā)中的一部分問題;但是還有一些問題需要我們使用自定義事件來完成。
那面就說一說如何用自定義事件實現(xiàn)代理。

我們還以預(yù)定手機為例,參考dom事件的原理來實現(xiàn)觀察者模式,用用戶的電話號碼作為類型,用戶的定購信息用一個回調(diào)函數(shù)來表示。

基本概念定義如下:

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

注:緩存列表,我將它定義為一個對象,用戶的電話號碼作為key,用戶的預(yù)定信息是個數(shù)組作為value。

代碼實現(xiàn)如下:

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

取消訂閱

當(dāng)然,現(xiàn)實中我們可以預(yù)定,那么也可以取消預(yù)定。其實取消預(yù)定的方式也比較簡單,就是將客戶從預(yù)定列表中清除出去。代碼實現(xiàn)如下:

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'); // 您還沒有預(yù)訂信息!

全局的觀察者模式

實現(xiàn)的代碼結(jié)構(gòu)如下:

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
    }
})();

優(yōu)點:

使用了全局的觀察者模式后,我們不用管商家是誰,只要他能提供我們所需要的東西即可;而且我們也避免了為不同的商家都創(chuàng)建listen,publish,remove方法,這樣可以減少資源的浪費。

缺點:

使用全局的觀察者模式會明顯降低對象之間的聯(lián)系。一些方法將會被隱藏,而有時我們恰恰需要這些方法的暴露。

是先訂閱,還是先發(fā)布

在我被問到這個問題時,我也是一愣,當(dāng)時腦袋里就冒出了‘你怎么不問是先有雞,還是先有蛋’這樣的想法。

按照我的理解我們實現(xiàn)觀察者模式,都是訂閱者先訂閱,然后接收發(fā)布者的通知消息。沒有反過來想,發(fā)布者先發(fā)布一條消息,然后等訂閱者接收,因為在我的想象中,如果沒有訂閱者,這消息怎么成功發(fā)布。

后來有人跟我說有這樣的業(yè)務(wù)實現(xiàn),當(dāng)時我就不假思索的問什么業(yè)務(wù),他說QQ的離線模式。這種先發(fā)布后訂閱的形式是將信息先存儲起來,等到訂閱者訂閱,就立即將信息發(fā)送給訂閱者。如:當(dāng)我們將QQ調(diào)到離線模式,我們就無法接收信息;當(dāng)我們將QQ調(diào)到登錄模式,就馬上收在離線模式期間接收到的信息。

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

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

設(shè)計模式周周講

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容