美文网首页
设计模式之发布订阅模式

设计模式之发布订阅模式

作者: GrowthCoder | 来源:发表于2018-04-25 23:19 被阅读0次

    最近公司在进行设计模式的分享,形成文章记录下来,同时也是对自己学习的总结,便于回顾,以及与大家交流。

    定义

    他定义了一种一对多的依赖关系,即当一个对象的状态发生改变的时候,所有依赖他的对象都会得到通知。

    优势

    • 使对象间松散的耦合在一起
    • 时间结耦,无需关注什么时候发布

    缺点

    • 创建订阅者本身要消耗一定的时间和内存,而 且当你订阅一个消息后,也许此消息最后都未发生,但这个订阅者会始终存在于内存中

    发布-订阅 VS 观察者

    发布-订阅是一种一对多的关系,一个对象状态发生改变时,所订阅他的对象也会得到通知;

    观察者模式,观察者与被观察者耦合的比较多,被观察者(Subject)中可以包含很多观察者(Observer),并且可以调用观察者中的函数,以此来通知观察者,是一个主动推送的过程,观察者得到推送进行相关更新。
    观察者模式

    代码实现

    发布—订阅模式可以用一个全局的 Event 对象来实现,订阅者不需要了解消息来自哪个发布者,发布者也不知道消息会推送给哪些订阅者,Event 作为一个类似“中介者” 的角色,把订阅者和发布者联系起来。

    class Event {
        constructor() {
            // 缓存列表
            this.clientList = {};
        }
        listen(key, fn) {
            // 订阅的消息添加进缓存列表
            (this.clientList[key] || (this.clientList[key] = [])).push(fn);
        }
        trigger() {
            const key = Array.prototype.shift.call(arguments),
                  fns = this.clientList[key];
    
            if( !fns || fns.length == 0) {
                return false;
            }
    
            for(let i = 0, fn; fn = fns[i++]; ) {
                // 依次trigger 同一个key的回调
                fn.apply(this, arguments);
            }
        }
        remove(key, fn) {
            const fns = this.clientList[key];
    
            if(!fns) {
                // 如果key对应的消息没有被订阅 直接返回
                return false;
            }
    
            if( !fn ) {
                // 如果没有传回调函数
                // 则remove所有事件
                fns && (fns.length = 0); 
            }else{
                // 匹配到对应的回调函数 则删除
                fns.filter(item => item != fn);
            }
        }
    }
    
    const env = new Event();
    env.listen('click', () => {
        console.log('aaa');
    })
    env.trigger('click', 'aa');
    env.remove('click');
    env.trigger('click'); 
    

    使用场景

    这个模式在前端领域是非常普遍的,尤其是在vue、vuex中

    • vue中的发布-订阅模式
    // vue中$on方法
    Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
        const vm: Component = this
        // 如果是数组,遍历为每个event绑定$on方法,
        if (Array.isArray(event)) {
          for (let i = 0, l = event.length; i < l; i++) {
            this.$on(event[i], fn)
          }
        } else {
          (vm._events[event] || (vm._events[event] = [])).push(fn)
          // optimize hook:event cost by using a boolean flag marked at registration
          // instead of a hash lookup
          if (hookRE.test(event)) {
            vm._hasHookEvent = true
          }
        }
        return vm
    
    // $off
    // 注销event,如果没有参数,注销所有event,如果只传方法名,则注销方法名对应的所有event
    Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
        const vm: Component = this
        // all 如果不传参注销所有event
        if (!arguments.length) {
          vm._events = Object.create(null)
          return vm
        }
        // array of events
        // 如果event是数组则递归注销事件
        if (Array.isArray(event)) {
          for (let i = 0, l = event.length; i < l; i++) {
            this.$off(event[i], fn)
          }
          return vm
        }
        // specific event
        const cbs = vm._events[event];
        // 本身不存在该事件 则直接返回
        if (!cbs) {
          return vm
        }
        // 如果只传了event参数,则注销该event下所有事件
        if (!fn) {
          vm._events[event] = null
          return vm
        }
        // 如果传入了event以及fn,则遍历寻找对应的方法并删除
        if (fn) {
          // specific handler
          let cb
          let i = cbs.length
          while (i--) {
            cb = cbs[i]
            if (cb === fn || cb.fn === fn) {
              cbs.splice(i, 1)
              break
            }
          }
        }
        return vm
      }
    
    
    • vuex中的发布-订阅模式
    /* 存放订阅者 */
    this._subscribers = [];
    
     /* 注册一个订阅函数,返回取消订阅的函数 */
      subscribe (fn) {
        const subs = this._subscribers
        if (subs.indexOf(fn) < 0) {
          subs.push(fn)
        }
        return () => {
          const i = subs.indexOf(fn)
          if (i > -1) {
            subs.splice(i, 1)
          }
        }
      }
      
      
     /* 调用mutation的commit方法 */
      commit (_type, _payload, _options) {
        // check object-style commit
        /* 校验参数 */
        const {
          type,
          payload,
          options
        } = unifyObjectStyle(_type, _payload, _options)
    
        const mutation = { type, payload }
        /* 取出type对应的mutation的方法 */
        const entry = this._mutations[type]
        if (!entry) {
          if (process.env.NODE_ENV !== 'production') {
            console.error(`[vuex] unknown mutation type: ${type}`)
          }
          return
        }
        /* 执行mutation中的所有方法 */
        this._withCommit(() => {
          entry.forEach(function commitIterator (handler) {
            handler(payload)
          })
        })
        /* 通知所有订阅者 */
        this._subscribers.forEach(sub => sub(mutation, this.state))
    
        if (
          process.env.NODE_ENV !== 'production' &&
          options && options.silent
        ) {
          console.warn(
            `[vuex] mutation type: ${type}. Silent option has been removed. ` +
            'Use the filter functionality in the vue-devtools'
          )
        }
      }
    
    • socket
      • 订阅器、发布器
    • 离线模式
    必须先订阅后发布么?

    有些情况下,需要先发布后订阅,比如QQ的离线消息,离线消息先缓存起来,等接受这上线之后,才可获取。

    相关文章

      网友评论

          本文标题:设计模式之发布订阅模式

          本文链接:https://www.haomeiwen.com/subject/jrpulftx.html