js常用設計模式4-發佈-訂閱模式 1,售樓消息訂閱 2,修改代碼,讓訂閱者只收到自己的消息 3,發佈-訂閱模式的通用實現 4,取消訂閱 5,全局的發佈-訂閱模式 6,總結

發佈-訂閱模式也叫觀察者模式,它定義對象之間一種一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都將得到通知。在javaScript開發中,我們一般用事件模型來代替傳統的發佈-訂閱模式。
DOM的addEventListener就是發佈-訂閱模式。

1,售樓消息訂閱

現實生活中,發佈-訂閱的例子很多。比如買房,我們去售樓處留下微信,當樓房有新消息時,售樓小姐就把最新消息推送給我們。如果不用這種方式,我們就需要隔一段時間向售樓小姐發送消息詢問,這麼一來,售樓小姐每天都要回覆上千人的信息轟炸,心態爆炸。
簡單實現發佈-訂閱:

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

salesOffices.clientList = []   //緩存列表,存放訂閱者的回調函數
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是發佈消息時帶上的參數
    }
}
//小明訂閱
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)

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

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)
    //去除該消息隊形的回調函數集合
    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,發佈-訂閱模式的通用實現

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

// 現在小明懷疑小紅還是有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函數,這個函數可以給所有對象都動態安裝發佈-訂閱功能(實際就是做一個淺拷貝):

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

現在我們來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
    }
    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,全局的發佈-訂閱模式

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

//全局訂閱對象--中介統一管理
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,總結

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章