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

141 阅读6分钟

前言

首先了解发布-订阅模式之前,可以先了解一下观察者模式,观察者模式比较简单,而发布-订阅模式是在观察者模式的基础上做了一个升级,观察者模式

正文

发布-订阅模式的初步了解

1.发布-订阅模式VS观察者模式

观察者模式

观察者.png

发布-订阅模式

发布订阅.png

两个模式都是实现对象之间的通信问题,这里的例子是房东和租客之间的关系,同样的需求,但两者的实现是存在极大差别的,这里的观察者/订阅者们的需求是什么,都是租房子,但两个模式的差别是什么,图片中很明显的可以看出:
第一个观察者模式,所有的租客想看更多的房子就需要找不同的房东,之间需要建立联系,他们之间的关系线也就相当复杂。
第二个发布-订阅模式,所有的租客只需要联系中介,就可以看四套房子,就不需要一一去找对应的房东。

这也就是为什么观察者模式称为一对多通信,因为他是从被观察者的角度出发,一个被观察者需要与多个观察者建立联系,而发布-订阅模式称为多对多通信,只需要通过一个中间层,就可以完成多对多之间的联系。

解决:都是解决通信问题
区别:比观察者模式多了一个中间层
好处:加中间层的好处,解耦、扩展性、灵活

2.发布-订阅模式的组成

发布-订阅模式是由发布者、订阅者、事件中心三个角色组成的一个模式

发布订阅流程.png

左边是具体关系, 右边是上面例子的关系,相结合更容易理解
用例子简单讲解一下,就是房东只需要把房子放到中介处,然后当租客想租房的时候,只要找中介,告诉中介想看哪些房子,中介就会找到对应的房东,拿到房子信息,并且通知租客。

发布-订阅模式的详细了解

1.角色的任务

事件总线

发布-订阅模式的任务首先收集订阅者提供的信息,也就是需要一个存放数据的容器,然后接收发布者发布的更新,查照是否存在对应的数据,最后进行通知订阅者。

订阅者

订阅者需要做什么? 一个是订阅事件,一个是触发事件,首先要订阅,肯定是有需求才会加入到这个行列中来的,那订阅者要做的就是订阅事件,把需求告诉事件中心,然后让事件中心去处理,同时也把处理方式告诉事件中心, 等到发布者的响应后,接收事件中心的通知。

发布者

发布者的需求很简单,就是再数据发生变化时,告诉事件中心,我某个数据发生了某种变化,其余的事情都交给事件中心处理。

2.实战示例

这里使用js来进行示例演示,下面是事件总线类,先过一遍代码
class EventEmitter {
    constructor() {
        this._events = {};
    }
    on(eventName, callback) {
        if (!this._events[eventName]) {
            this._events[eventName] = [callback];
            return;
        }
        this._events[eventName].push(callback);
    }
    emit(eventName, datas) {
    if(!this._events[eventName]) return;
        this._events[eventName].forEach(cb => cb(datas))
    }
    off(eventName, callback) {
        if (!callback) this._events[eventName] = undefined
        if (!this._events[eventName]) return;
        this._events[eventName] = this._events[eventName].filter(item => { return item != callback; })
    }
}

来看看事件总线具体做了哪些事
解析:

  • 这里创建了一个EventEmitter类,
  • 构造函数定义了一个_events对象,也就是用来存放订阅者的容器。 了解一下_events的存放格式
_events = {
    事件名称1 : [回调函数1,回调函数2,...],
    事件名称2 : [回调函数1,回调函数2,...],
}
//事件名称可以看作是和发布者的绑定,
//回调函数就是与该发布者有关的订阅者,
//这样看是不是和观察者模式就一样了。
//区别:观察者模式是把观察者存放在被观察者中的数组中,
//而发布-订阅模式是将发布者和订阅者以键值对的形式存放再事件总线中
  • on()具体实现:首先有两个参数(事件名称,回调函数),事件名称可以看作是寻找某个发布者,而回调函数就是订阅者本身,进行判断,判断数组中是否存在该事件名称,不存在就创建一个,并把callback存入,如果存在直接将callback存入,这个过程就是订阅事件,也就是建立与发布者的联系。
  • emit()具体实现:也是有两个参数(事件名称,传回的数据),在this._events中查找对应事件名称的value值,也就是一个存放callback的数组,遍历数组,执行其中的回调函数。
  • off()具体实现:一个类存在绑定,完整一点肯定也需要一个解绑,释放存储空间,

这就是事件总线中完成的事情

on方法的调用
   const events = new EventEmitter()
   events.on("newListener", function(eventName) {  
       console.log(`eventName`, eventName)
   })
   events.on("hello", function() {
       console.log("hello");
   })
   let cb = function() {
       console.log('cb');
   }
   events.on("hello", cb) 
   events.emit("hello") // hello   cb

解析:

  • 首先创建一个EventEmitter实例
  • 调用on方法,传入事件名称和回调函数
  • 总共调用了三次on方法
  • 调用emit方法,进行执行某个时间名下的回调函数
off方法的调用

还是根据上面那个例子,我们来看看off方法的使用,也就是删除的使用。

//第一种使用
events.off("hello", cb) //这里把hello事件名称下的cb回调删除了,这样调用emit就不会执行cb的回调函数了
//再次打印
events.emit("hello") // hello

//第二种使用
events.off("hello") //不带回调函数名称,就直接把全部回调函数都删除
 //再次打印
events.emit("hello") //
emit方法的使用


//修改一下其中一个回调函数
events.on("hello", function(datas) {
   console.log("这是带data的回调" + datas);
})

//使用emit时带上第二个参数
events.emit("hello", "数据更新") //这是带data的回调数据更新
//这样就可以获取到返回回来的数据

可以稍微完善一下回调函数,做一个参数校验,简单写一下,根据直接的想法进行实现

events.on("hello", function(datas) {
    if(!datas) return console.log("没有返回数据")
    console.log("这是带data的回调" + datas);
})

总结

最后再来总结一下,发布-订阅模式是观察者模式的升级版,在需要通信的双方之间添加了一个中间层,也就是事件总线,时间总线把原本属于通信双方需要做的事情,收入到自己这边,由自己来完成,双方只需要调用其中的方法,并且把数据传入就完成任务,减轻了通信双方的耦合性,变得更加灵活。如果觉得总结看不懂的话,建议从头再看一遍~

本章完