发布订阅模式及多种实现方式:


1、何为观察者模式?

观察者模式,又可以称之为发布-订阅模式,观察者,顾名思义,就是一个监听者,类似监听器的存在,一旦被观察/监听的目标发生的情况,就会被监听者发现,这么想来目标发生情况到观察者知道情况,其实是由目标将情况发送到观察者的。

观察者模式多用于实现订阅功能的场景,例如微博的订阅,当我们订阅了某个人的微博账号,当这个人发布了新的消息,就会通知我们。

2、观察者模式解决的问题?

解决主体对象与观察者之间功能耦合。

3、实现观察者步骤:

(1)创建一个观察者:

把观察者或者消息系统看作一个对象,那么他应该包含两个方法,一个是接受消息,一个是向中转站发送相应消息。

首先,我们把需要的把观察者对象创建出来,有一个消息容器和三个方法,分别是,订阅消息的方法,取消订阅消息的方法,发送订阅消息的方法。

var Observer = (function(){
   //防止消息队列暴露而被篡改,所以将消息容器作为静态私有变量保存
   var __messsages = {};
   return {
       //注册信息接口 
       regist: function(){},
       //发布信息的接口
       fire: function(){},
       //移除信息接口
       remove: function(){}        
   }   
})();

观察者对象的雏形出来了,我们需要做的事情就是实现这三个方法

(2)实现订阅方法:

我们首先实现消息注册方法,注册方法的作用是将订阅者注册的消息推入到消息队列中,因此我们需要接受两个参数:消息类型和以及相应的处理动作,在推入到消息队列时如果此消息不存在应该创建一个该消息类型并将该消息放入到消息队列中,如果此消息存在则应该将消息执行方法推入该消息对应的执行方法队列中,这么做目的是保证多个模块注册同一个消息能顺利执行。

regist: function(type,fn){
    //如果此消息不存在,则应该创建一个该消息类型
    if(typeof __messages[type] === 'undefined'){
        //将对象推入到该消息对应的动作执行队列中
        __messages[type] = [fn];
    //如果此消息存在
    }else{
        //将动作方法推入该消息对应的动作执行序列中  
     __messages[type].push(fn);  
    }   
}

(3)实现发布方法:

对于发布消息方法,其功能是当观察者发布一个消息时将所有订阅者订阅的消息一次执行。

故应该接受两个参数,消息类型以及动作执行时需要传递的参数,当然在这里消息类型是必须的。在执行消息队列之前校验消息的存在是很有必要的。

然后遍历消息执行方法队列,并依此执行。

然后将消息类别以及传递的参数打包后依次传入消息队列执行方法中。

fire: function(type,args){
    //如果该消息没有被注册,则返回
    if(!__messages[type]){
        return ;
    //定义消息信息
    var events = {
        type:type,
        args:args||{}
    },
    i=0;
    len =  __messages[type].length;
    for(;i<len;i++){
        //依次执行注册的消息对应的动作序列
        __messages[type][i].call(this,events);
    }
}

(4)实现注销方法:

最后是消息注销方法,其功能是将订阅者注销的消息从消息队列清除,因此我们也需要两个参数,即消息类型以及执行的某一个动作。当然为了避免删除消息动作时消息不存在情况的出现,对消息队列中消息的存在性校验也很有必要的。

remove:function(type,fn){
    //如果消息动作队列存在
    if(__messages[type] instanceof Array){
        //从最后一个消息动作遍历
        var i = __messages[type].length-1;
        for(;i>=0;i--){
            //如果存在该动作则在消息动作中移除相应动作
            __messages[type][i] === fn && __messages[type].splice(i,1);
          }  
    }
}

3、其他实现观察者方式(使用ES6 Class 方式实现链式调用的观察者模式):

/**
 * 发布订阅模式(观察者模式)
 * handles: 事件处理函数集合
 * on: 订阅事件
 * emit: 发布事件
 * off: 删除事件
**/

class PubSub {
  constructor() {
    this.handles = {};
  }

  // 订阅事件
  on(eventType, handle) {
    if (!this.handles.hasOwnProperty(eventType)) {
      this.handles[eventType] = [];
    }
    if (typeof handle == 'function') {
      this.handles[eventType].push(handle);
    } else {
      throw new Error('缺少回调函数');
    }
    return this;
  }

  // 发布事件
  emit(eventType, ...args) {
    if (this.handles.hasOwnProperty(eventType)) {
      this.handles[eventType].forEach((item, key, arr) => {
        item.apply(null, args);
      })
    } else {
      throw new Error(`"${eventType}"事件未注册`);
    }
    return this;
  }

  // 删除事件
  off(eventType, handle) {
    if (!this.handles.hasOwnProperty(eventType)) {
      throw new Error(`"${eventType}"事件未注册`);
    } else if (typeof handle != 'function') {
      throw new Error('缺少回调函数');
    } else {
      this.handles[eventType].forEach((item, key, arr) => {
        if (item == handle) {
          arr.splice(key, 1);
        }
      })
    }
    return this; // 实现链式操作
  }
}

// 下面做一些骚操作
let callback = function () {
  console.log('you are so nice');
}

let pubsub = new PubSub();
pubsub.on('completed', (...args) => {
  console.log(args.join(' '));
}).on('completed', callback);

pubsub.emit('completed', 'what', 'a', 'fucking day');
pubsub.off('completed', callback);
pubsub.emit('completed', 'fucking', 'again');

 


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM