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

简介

发布-订阅模式也可称为观察者模式,使用非常广泛,也非常有用。通过发布者遍历依赖通知,监听者监听添加依赖。在Vue2.x中的响应式原理也是通过发布-订阅模式实现

DOM事件

非常常用的DOM事件监听就是发布-订阅模式的实现。

document.body.addEventListener('click',function(){
  alert('点击');
},false)

我们不知道事件什么时候会被出触发,所以使用监听的方式进行监听,当触发该事件的时候就会调用回调函数。

自定义事件

很多时候,我们不是想监听事件,而是监听某种操作结果,在某个时候触发,可以自己实现类似监听器的功能。

const m = {};
m.deps = [];//依赖数组
m.listen = function(dep){
  this.deps.push(dep);
}
m.trigger = function(){
  for(let i = 0;i<this.deps.length;i++){
    this.deps[i].apply(this,arguments);
  }
}

// 订阅者1
m.listen(function(){
  console.log('添加订阅1');
})
// 订阅者2
m.listen(function(){
  console.log('添加订阅2');
})
m.trigger();// 发布者
m.trigger();// 发布者

上述代码就是一个简单的发布-订阅模式的实现。但是上面实现任然存在一个缺点。订阅者1订阅消息1,订阅者2订阅消息2,但是触发的时候订阅者1会接收到消息2,订阅者2会接收到消息1,这在根本上是错误的。所以需要实现一个更加通用的发布-订阅模式。原理就是添加key进行区分。

const m = {
  deps:{}
};
m.listen = function(key,dep){
  if(!this.deps[key]){
    this.deps[key] = [];
  }
  this.deps[key].push(dep);
}
m.trigger = function(key,fn){
  const fns = this.deps[key];
  if(!fns||fns.length===0){
    return false
  } 
  for(let i = 0;i<fns.length;i++){
    fns[i].apply(this,arguments);
  }
}

// 订阅者1
m.listen('订阅者1',function(){
  console.log('添加订阅1');
})
// 订阅者2
m.listen('订阅者2',function(){
  console.log('添加订阅2');
})
m.trigger('订阅者1');// 发布者
m.trigger('订阅者2');// 发布者

result:
添加订阅1
添加订阅2

在此基础上,可以添加移除订阅功能

m.remove = function(key,fn){
  const fns = this.deps[key];
  if(!fns||fns.length===0){
    return false
  }
  if(!fn){
    fns.length = 0;
    return
  }
  for(let i=0;i<fns.length;i++){
    if(fns[i]===fn){
      fns.splice(i,1);
      break
    }
  }
}

通用的发布-订阅模式

上述代码的实现虽说可以完成目的,但是还是存在问题的,假如现在我们想为其他对象添加发布-订阅功能,那么要把全部代码复制粘贴一遍?显然是不好的,所以需要设计一个通用的发布-订阅模式

const Event = {
  deps:{},
  listen(key,dep){
    if(!this.deps[key]){
      this.deps[key] = [];
    }
    this.deps[key].push(dep);
  },
  trigger(key,fn){
    const fns = this.deps[key];
    if(!fns||fns.length===0){
      return false
    } 
    for(let i = 0;i<fns.length;i++){
      fns[i].apply(this,arguments);
    }
  },
  remove(key,fn){
    const fns = this.deps[key];
    if(!fns||fns.length===0){
      return false
    }
    if(!fn){
      fns.length = 0;
      return
    }
    for(let i=0;i<fns.length;i++){
      if(fns[i]===fn){
        fns.splice(i,1);
        break
      }
    }
  }

};

function insEvent (obj){
  for(const event in Event){
    if(Event.hasOwnProperty(event)){
      obj[event] = Event[event];
    }
  }
}

const obj1 = {};
const obj2 = {};
insEvent(obj1);
insEvent(obj2);

先发布后订阅

传统的发布-订阅模式都是先订阅,然后发布者发布事件。但是有时任然会存在需要先发布后订阅的场景,比如离线事件,我们可以先将事件进行缓存,等待真正监听的时候进行调用,下面是先发布后订阅的简单实现

const Event = (function(){
  const cache = {};
  return  {
    listen(key,callback){
      fns = cache[key];
      if(!fns||fns.length===0){
        return false
      }
      for(let i =0;i<fns.length;i++){
        callback(fns[i].apply(this,arguments));
      }
    },
    trigger(){
      const key = Array.prototype.shift.apply(arguments,arguments);
      const value = Array.prototype.shift.apply(arguments,arguments);
      const fn = function(){
        return value
      }
      if(!cache[key]){
        cache[key] = [];
      }
      cache[key].push(fn);
    }
  }
})()
// 先发布后订阅,离线缓存等
Event.trigger('click',1);
Event.trigger('click',2);
Event.listen('click',function(res){
  console.log(res);//  1 2
})

总结

发布-订阅模式是非常有用的设计模式,在架构中也不少见,其优点在于减少了时间上的耦合,和对象之间的耦合,但是也可能是造成缺点的原因,耦合性太低不一定是好事。。对象之间的联系可能不容易发现,对后期维护造成不必要的开销

发布了85 篇原创文章 · 获赞 62 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/qq_36754767/article/details/105041938