js常用設計模式4-發(fā)布-訂閱模式

發(fā)布-訂閱模式也叫觀察者模式,它定義對象之間一種一對多的依賴關系,當一個對象的狀態(tài)發(fā)生改變時,所有依賴于它的對象都將得到通知。在javaScript開發(fā)中,我們一般用事件模型來代替?zhèn)鹘y(tǒng)的發(fā)布-訂閱模式。
DOM的addEventListener就是發(fā)布-訂閱模式。

1,售樓消息訂閱

現(xiàn)實生活中,發(fā)布-訂閱的例子很多。比如買房,我們?nèi)ナ蹣翘幜粝挛⑿?,當樓房有新消息時,售樓小姐就把最新消息推送給我們。如果不用這種方式,我們就需要隔一段時間向售樓小姐發(fā)送消息詢問,這么一來,售樓小姐每天都要回復上千人的信息轟炸,心態(tài)爆炸。
簡單實現(xiàn)發(fā)布-訂閱:

  • 首先確定誰是信息發(fā)布者(售樓處)
  • 給發(fā)布者一個消息緩存列表,用于存放訂閱者的回調(diào)函數(shù),以便通知訂閱者(售樓處的花名冊)
  • 發(fā)布消息的時候,發(fā)布者會遍歷緩存列表,依次觸發(fā)每個回調(diào)函數(shù)(遍歷花名冊,挨個發(fā)微信)
// js中的事件模型就是發(fā)布-訂閱模式,也叫觀察者模式,其實是1對p的問題,p指代對象,這就是個多p問題
var salesOffices = {}  //定義售樓處

salesOffices.clientList = []   //緩存列表,存放訂閱者的回調(diào)函數(shù)
salesOffices.listen = function (fn) {  //增加訂閱者
    this.clientList.push(fn)      //訂閱者的消息添加進緩存列表
}
salesOffices.trigger = function () {
    for (var i = 0, fn; fn = this.clientList[i++];) {
        fn.apply(this, arguments)    //arguments是發(fā)布消息時帶上的參數(shù)
    }
}
//小明訂閱
salesOffices.listen(function (price, squareMeter) {
    console.log('價格:' + price)
    console.log('面積:' + squareMeter)
})
//小紅訂閱
salesOffices.listen(function (price, squareMeter) {
    console.log('價格:' + price)
    console.log('面積:' + squareMeter)
})

salesOffices.trigger(1000000, 100)
salesOffices.trigger(2000000, 90)

這是一個簡單的售樓消息發(fā)布-訂閱模式,但有一些問題,發(fā)布的時候小明和小紅都可以接收到所有人訂閱的消息,這很明顯是不合理的

2,修改代碼,讓訂閱者只收到自己的消息

增加一個用于標識的key,讓訂閱者只收到自己感興趣的消息

// 剛才那個沒有一一對應的關系,流程很不清晰,讓人懷疑里面有py交易
var salesOffices = {}
salesOffices.clientList = {}
salesOffices.listen = function (key, fn) {
    if (!this.clientList[key]) {
        this.clientList[key] = []
    }
    this.clientList[key].push(fn)
}
salesOffices.trigger = function () {
    var key = Array.prototype.shift.call(arguments)
    //去除該消息隊形的回調(diào)函數(shù)集合
    fns = this.clientList[key]
    //如果沒有訂閱該消息,則返回
    if (!fns || fns.length === 0) {
        return false
    }
    for (var i = 0, fn; fn = fns[i++];) {
        fn.apply(this, arguments)
    }
}
//squareMeter88就是我們添加的key
salesOffices.listen('squareMeter88', function (price) {
    console.log('價格:' + price)
})
salesOffices.listen('squareMeter140', function (price) {
    console.log('價格:' + price)
})

salesOffices.trigger('squareMeter88', 1000000)
salesOffices.trigger('squareMeter140', 2000000)

3,發(fā)布-訂閱模式的通用實現(xiàn)

如果小明和小紅換了一家售樓部,那么又需要重新訂閱,這無疑是很麻煩的,我們需要實現(xiàn)一個通用的全局訂閱。
我們先將發(fā)布-訂閱的功能提取出來,放在一個單獨的對象中:

// 現(xiàn)在小明懷疑小紅還是有py交易,所以想換一個,做一個通用的觀察者
var event = {
    clientList: {},
    listen: function (key, fn) {
        if (!this.clientList[key]) {
            this.clientList[key] = []
        }
        this.clientList[key].push(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, fn; fn = fns[i++];) {
            fn.apply(this, arguments)
        }
    }
}

然后在定義一個installEvent函數(shù),這個函數(shù)可以給所有對象都動態(tài)安裝發(fā)布-訂閱功能(實際就是做一個淺拷貝):

var installEvent = function (obj) {
    for (var key in event) {
        obj[key] = event[key]
    }
}

現(xiàn)在我們來try一try剛才寫好的功能:

var salesOffices = {}
installEvent(salesOffices)

// 小明訂閱消息
salesOffices.listen('squareMeter88', function (price) {
    console.log('價格:' + price)
})
salesOffices.listen('squareMeter140', function (price) {
    console.log('價格:' + price)
})

//小紅訂閱消息
salesOffices.trigger('squareMeter88', 1000000)
salesOffices.trigger('squareMeter140', 2000000)

4,取消訂閱

有一天,小明不想奮斗了,小明的阿姨送了小明一棟樓,小明需要取消和售樓小伙的py交易:

var event = {
    clientList: {},
    listen: function (key, fn) {
        if (!this.clientList[key]) {
            this.clientList[key] = []
        }
        this.clientList[key].push(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, fn; fn = fns[i++];) {
            fn.apply(this, arguments)
        }
    }
}

event.remove = function (key, fn) {
    var fns = this.clientList[key]
    if (!fns) {
        return false
    }
    //不傳就是撤銷所有
    if (!fn) {
         fns && (fns.length=0)
    }
    else {
        for (var i = 0, _fn; _fn = fns[i++];) {
            if (_fn = fn) {
                fns.splice(i, 1)
            }
        }
    }
}

var installEvent = function (obj) {
    for (var key in event) {
        obj[key] = event[key]
    }
}

var salesOffices = {}
installEvent(salesOffices)

salesOffices.listen('squareMeter88', f1 = function (price) {
    console.log('價格:' + price)
})
salesOffices.listen('squareMeter140', function (price) {
    console.log('價格:' + price)
})

// salesOffices.trigger('squareMeter88', 1000000)
salesOffices.remove('squareMeter88', f1)

salesOffices.trigger('squareMeter140', 2000000)

5,全局的發(fā)布-訂閱模式

之前實現(xiàn)的售樓消息訂閱,還存在兩點問題:
每個被做了淺拷貝的對象,都有所有的listen,trigger,和緩存列表clientList,這個有些浪費
小明和售樓處還有一些耦合。小明需要知道售樓小姐salesOffices才可以訂閱,如果小明又想知道salesOffices2的信息,那么又需要訂閱salesOffices2,這個操作比較麻煩。
我們可以使用一個中介,相當于賣方的中介公司,用來處理所有的訂閱請求。售樓處通過中介來發(fā)布消息,客戶通過中介來訂閱消息。
我們創(chuàng)建一個全局的Event對象,作為中介者:

//全局訂閱對象--中介統(tǒng)一管理
var Event = (function () {
    var clientList = {},
        listen,
        trigger,
        remove;

    listen = function (key, fn) {
        if (!clientList[key]) {
            clientList[key] = []
        }
        clientList[key].push(fn)
    }
    trigger = function () {
        var key = Array.prototype.shift.call(arguments)
        fns = clientList[key]

        if (!fns || fns.length === 0) {
            return false
        }
        for (var i = 0, fn; fn = fns[i++];) {
            fn.apply(this, arguments)
        }
    }
    remove = function (key, fn) {
        var fns = clientList[key]
        if (!fns) {
            return false
        }
        //不傳就是撤銷所有
        if (!fn) {
            fns && fns.length
        }
        else {
            for (var i = 0, _fn; _fn = fns[i++];) {
                if (_fn = fn) {
                    fns.splice(i, 1)
                }
            }
        }
    }
    return {
        listen,
        trigger,
        remove
    }
})()

Event.listen('squareMeter88', function (price) {
    console.log('價格:' + price)
})
Event.trigger('squareMeter88', 1000000)

6,總結(jié)

發(fā)布-訂閱模式的有點很明顯,一是時間上的解耦,二是對象上的解耦。發(fā)布-訂閱模式的應用相當廣泛,它和中介者模式有一些類似之處,可以幫助實現(xiàn)中介者模式。
個人認為,在處理的事情上,發(fā)布-訂閱模式功能相對單一,主要就是收發(fā)消息,解決的是發(fā)布者和訂閱者之間的多對多關系;中介者模式功能五花八門,怎么寫都可以,解決的是對象與對象之間的多對多關系,將對象與對象的網(wǎng)狀關系,解耦成中介者與對象之間的一對多關系。

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

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

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