公布-订阅模式与观察者模式

  • 公布-订阅模式与观察者模式
  • 1.什么是公布-订阅模式
  • 2.举例

    • 1.创立一个公布-订阅管理器
    • 2.页面
    • 3.业务代码
  • 3.公布-订阅模式与观察者模式有什么区别
  • 参考文献
  • 源码查看

1.什么是公布-订阅模式

公布订阅模式罕用于异步编程,在浏览器中,咱们调用document.body.addEventListener('click', function(){ })就是一种这种模式的实现,这段代码是在订阅一个body上的点击事件,当用户点击body后,dom就会公布一个类型为click的音讯,并且执行咱们注册的回调事件,这时咱们就能监听到用户点击行为了。

举一个企业招聘的例子,X公司的HR在智联在智联招聘网站上公布一条招聘前端工程师的信息,当你登陆该网站后订阅前端招聘告诉,只有有企业公布起那段招聘信息,你都会收到音讯。

那么,你的订阅行为就是subscribe,HR公布音讯行为就是publish,网站就是一个音讯中介,负责将HR公布的信息发送到每个订阅人的手机上。

咱们总结一下订阅模式三要素

  • 1.订阅者-subscriber
  • 2.公布音讯者-publisher
  • 3.中介公司

2.举例

上面咱们举一个购物网站的例子,设计一个页面,蕴含告诉栏/购物栏/订单列表三个局部

当购买意见商品后,刷新告诉栏和订单列表。

在这个例子中,触发按钮购买公布一条音讯将商品数量增加到订单中,订阅者在收到新商品后刷新告诉栏和订单列表.

1.创立一个公布-订阅管理器

蕴含三个办法

  • 1.create():创立实例
  • 2.publish():公布一条音讯
  • 3.subscribe():订阅音讯
/** * 创立订阅-公布模式实例 * create()办法创立出的实例是单例模式,每个命名空间都有一个独立的单例对象。 */const PSManager = (function () {    var namespacesCache = {}    var listenerMap = new Map();// key为音讯类型    /**     * 公布音讯     * @param {string} type   音讯类型     * @param  {...any} args 音讯内容     */    var publish = function(type, ...args){        if (listenerMap.get(type)) {            var listeners = listenerMap.get(type);            listeners.forEach(callback => {// 订阅池里的所有事件全副触发                callback && callback.apply(null, args)            });        }    }    /**     * 订阅音讯     * @param {string} type     音讯类型     * @param {*} callback      收到音讯后执行回调函数     */    var subscribe = function(type, callback){        if (listenerMap.get(type)) {            var listeners = listenerMap.get(type);            listeners.push(callback)// 增加新的订阅        } else {            listenerMap.set(type, [callback])        }    }    /**     * 创立一个公布-订阅模式的实例     * @param {*} namespace     命名空间,解决多个模块调用呈现抵触,不传默认'_default'     * @returns 实例     */    var create = function(namespace = '_default'){        return namespacesCache[namespace] ? namespacesCache[namespace] : {            publish, subscribe        }    }    return {        publish, subscribe, create    }})()

2.页面

<!DOCTYPE html><html><style>    button { margin-right: 16px; }    div { margin-bottom: 20px; }</style><head>    <meta charset="utf-8">    <title>公布-订阅模式</title></head><body>    <div>        <span>商品数:0</span>        <span style="margin-left: 16px;">您刚购买了0个手机</span>    </div>    <div>        <span>手机8000元/部</span> <button>购买</button>        <span>玩具200元/个</span> <button>购买</button>         <span>鞋子400元/双</span> <button>购买</button>     </div>    <ul>        <li>手机0部,总计0元</li>        <li>玩具0个,总计0元</li>        <li>鞋子0双,总计0元</li>    </ul></body><script src="./index.js"></script></html>

3.业务代码

// 状态管理器,存储商品数量、单价,总生产价格等信息var State = function(){    var state = {        phone: [8000,0,0], toy: [200,0,0], shoes: [400,0,0]    }    return {        add:function(key,count){// 增加一个商品            state[key] === undefined ? state[key][1] = 0 : state[key][1] += count;            return this;        },        getCount: function(key){// 某类商品数量            return state[key] === undefined ? 0 : state[key][1]        },        getPrice: function(key){// 商品总价 = 数量 * 单价            return state[key] === undefined ? 0 : state[key][1] * state[key][0];        },        sum: function(){// 总商品数            return Object.keys(state).reduce((ret, key) => ret += state[key][1], 0);        },        getTip: function(key){// 提醒语            switch(key){                case 'phone':                    return [`手机${this.getCount(key)}部,总计${this.getPrice(key)}元`, '您刚购买了1部手机'];                case 'toy':                    return [`玩具${this.getCount(key)}个,总计${this.getPrice(key)}元`, '您刚购买了1个玩具']                case 'shoes':                    return [`鞋子${this.getCount(key)}双,总计${this.getPrice(key)}元`, '您刚购买了1双鞋子']                default:                    return [];            }        }    }}()// 具体业务代码window.onload = function () {    var btns = document.getElementsByTagName('button')    var spans = document.getElementsByTagName('span')    var lies = document.getElementsByTagName('li')    // 公布音讯    btns[0].onclick = function () {// 购买手机        PSManager.create('order_list').publish('phone',1)    }    btns[1].onclick = function () {// 购买玩具        PSManager.create('order_list').publish('toy',1)    }    btns[2].onclick = function () {// 购买鞋子        PSManager.create('order_list').publish('shoes',1)    }    // 订阅音讯    PSManager.create('order_list').subscribe('phone',function(count){        lies[0].innerText = State.add('phone', count).getTip('phone')[0];// 刷新告诉栏        spans[0].innerText = `总商品数:${State.sum()}`;// 刷新告诉栏        spans[1].innerText = State.getTip('phone')[1]// 刷新订单列表    })    PSManager.create('order_list').subscribe('toy',function(count){        lies[1].innerText = State.add('toy', count).getTip('toy')[0];        spans[0].innerText = `总商品数:${State.sum()}`;        spans[1].innerText = State.getTip('toy')[1]    })    PSManager.create('order_list').subscribe('shoes',function(count){        lies[2].innerText = State.add('shoes', count).getTip('shoes')[0];        spans[0].innerText = `总商品数:${State.sum()}`;        spans[1].innerText = State.getTip('shoes')[1]    })}

3.公布-订阅模式与观察者模式有什么区别

咱们把下面的发布者(publisher)改为主题(Subject),订阅者(Subsriber)改为察看着者Observer就能够变为观察者模式了。

// 主题var Subject = (function () {    var namespacesCache = {};// 命名空间    var observerMap = new Map();// 观察者汇合    var registObserver = function (type, func) {        observerMap.get(type) ? observerMap.get(type).push(func) : observerMap.set(type, [func])    }    /**     * 勾销观察者     * @param {string} type      * @param {Function} func 观察者     */    var removeObserver = function (type, func) {         if (removeObserver.get(type)) {            removeObserver.get(type) = removeObserver.get(type).filter(fn => fn !== func)        }     }    /**     * 告诉观察者     * @param {string} type      */    var notifyObservers = function (type, ...args) {        if (observerMap.get(type)) {            Array.prototype.forEach.apply(observerMap.get(type), [function(callback,i,ary){                callback && callback.apply(null,args);// 执行回调            }])        }    }    /**     * 创立主题     * @param {string} namespace      * @returns      */    var create = function(namespace = '_default'){        return namespacesCache[namespace] ? namespacesCache[namespace] : namespacesCache[namespace] = {registObserver, removeObserver,notifyObservers}    }    return { create }})()

调用形式与公布-订阅模式一样

    btns[0].onclick = function () {// 购买手机        Subject.create('order_list').notifyObservers('phone',1)    }    Subject.create('order_list').registObserver('toy',function(count){        lies[1].innerText = State.add('toy', count).getTip('toy')[0];        spans[0].innerText = `总商品数:${State.sum()}`;        spans[1].innerText = State.getTip('toy')[1]    })

因而我认为观察者模式===公布-订阅模式

如果非要说出他们之间的区别,我认为公布-订阅模式比观察者模式解耦能力更强。

参考文献

  • JavaScript设计模式与开发实际. 曾探. [D]
  • Head First设计模式. Freeman. [D]

源码查看

  • 代码实现都放在了github上