设计模式——发布订阅模式

870 阅读3分钟

「这是我参与2022首次更文挑战的第4天,活动详情查看:2022首次更文挑战

简介

发布订阅模式,又叫观察者模式。学过Vue的肯定都听过,今天我们就把它讲彻底。

它定义对象之间的一对多依赖关系,当对象状态发生变化时,所有依赖于它的对象都会得到通知。 面试中50%的几率会遇到coding发布订阅的题。

案例

小红从事房屋售卖工作。小明今天找他买房,结果今天没有可售卖的房子,于是小明要了小红的手机号,每天都会给她打电话询问是否有房源。同时还有小李、范闲、凤年等很多人会给小红打电话,不到一个月小红就要辞职,因为她每天都要接上千个电话。

最后她想了一个办法:把所有用户的手机号收集起来,等有房源的时候,挨个给用户发一条短信就可以了。 下面我们来实现一下

const xiaohong = (function(){
    const phoneList = [];
    function add(phone) {
        phoneList.push(phone)
    }
    function call() {
        phoneList.forEach(item => {
            console.log('小红给' + item + '发了通知!')
        })
    }
    return {
        add,
        call
    }
})()
xiaohong.add(12345) 
xiaohong.add(67891) 
xiaohong.add(24680) 
xiaohong.call() 

优点:

  1. 小红不需要每天接大量的电话。
  2. 小红和客户之间没有强耦合,即使小红更换了手机号,也不会影响用户收到通知。

小红作为发布者,当出现新的订阅者时,发布者不需要做任何改动。发布者自己做出改变时,也不会影响到订阅者。

DOM绑定事件

给一个DOM绑定事件,也是发布订阅的一种使用场景。将事件注册进dom中,并取好名称,当DOM触发同名的事件时,将会执行已注册的事件。

document.body.addEventListener( 'click', function(){
    alert(2);
}, false );   
document.body.click();    // 模拟用户点击

取消订阅

小明把自己的手机号给小红后,第二天自己的基金亏损严重,没有足够的钱买房子了。于是告诉小红有房源后也不需要通知自己了。于是:小红把call的次数记录下来。当有新的订阅者时,就会把按照记录的发布次数,再次发布给新的订阅者。

const xiaohong = (function(){
    let phoneList = [];
    ...
    function del(phone) {
        phoneList = phoneList.filter((item) => phone !== item);
    }
    return {
        ...
        del
    }
})()

先发布后订阅

如果先发布,后订阅。那么订阅者还能收到之前发布的信息吗?应该怎样实现这个功能?

const xiaohong = (function(){
    let phoneList = [];
    let callCount = 0;
    function add(phone) {
        phoneList.push(phone)
        for (let i = 0; i < callCount; i++) {
            console.log('小红给' + phone + '发了通知!')
        }
    }
    function call() {
        phoneList.forEach(item => {
            console.log('小红给' + item + '发了通知!')
        })
        callCount++
    }
    return {
        add,
        call
    }
})()
xiaohong.call() 
xiaohong.call() 
xiaohong.call() 
xiaohong.add(12345)

面试遇到怎么写

上面的例子写的都比较简单,面试的时候肯定要写的更完善,我们就写一个完整版的例子:

  1. 事件订阅$on;
  2. 事件派发$emit;
  3. 取消订阅$off;
  4. 先发布后订阅;
const eventBus = (function (){
    const data = {};
    const emitobj = {};
    function $on(key, fn) {
        data[key] = fn;
        if (emitobj[key]) {
            data[key]();
            delete emitobj[key];
        }
    }
    function $emit(key, ...args) {
        if (data[key]) {
            data[key](...args)
        } else {
            emitobj[key] = true;
        }
    }
    function $off(key) {
        delete data[key];
    }
    return {
        $on,
        $emit,
        $off,
    }
})()

想要更好可以把data[key]、emitobj[key]改进为数组,这样同一个key可以派发多个事件。

总结

满足事件发布订阅的三要素:

  1. 首先要指定好谁充当发布者(比如售楼处);
  2. 然后给发布者添加一个缓存列表,用于存放回调函数以便通知订阅者(售楼处的花名册);
  3. 最后发布消息的时候,发布者会遍历这个缓存列表,依次触发里面存放的订阅者回调函数(遍历花名册,挨个发短信)。