最近在看vue的源码,发现用到了js的发布订阅模式,所以就恶补了一下,这。。。到底是个啥?
说到发布订阅模式,我看到很多人将它和观察者模式说成一个,看了这么多的博文介绍,我还是倾向于他们两个是两种模式。下面来说一下我的理解吧。
可以参考这篇https://www.cnblogs.com/lovesong/p/5272752.html来看一下原理上的区别,虽然道理是理解的,但是关于观察、发布、订阅的实现以及调度中心这个概念是比较模糊的,不太清楚他们具体是怎么实现的。所以就来分析一下,希望跟我一样对这块儿模式的实现有困惑的小伙伴儿能清晰。
观察者模式
1、定义(摘自百度百科):观察者模式(有时又被称为模型(Model)-视图(View)模式、源-收听者(Listener)模式或从属者模式)是软件设计模式的一种。在此种模式中,一个目标物件管理所有相依于它的观察者物件,并且在它本身的状态改变时主动发出通知。这通常透过呼叫各观察者所提供的方法来实现。此种模式通常被用来实现事件处理系统。
2、其中包括的两个定义:观察者和目标(被观察者),通常来说,二者是一对多的,也有多对多的模式。
3、观察者和目标怎么联系:目标维护一个观察者列表,当数据变化时,通过调用观察者的update接口实现更新。
4、实现:
//目标基类(被观察者)
Class Subject{
//一个目标包含自己本身的数据和一个观察者的列表
constructor(data){
this.observerList = [];
this.data = data;
}
//向列表中添加观察者(即观察动作的实现)
add(obj){
if (arguments.length >= 1) {
Array.from(arguments).forEach(item => this.observerList.push(item));
}
//向列表中移除观察者
remover(observer){
let i = this.observerList.findIndex(ele => ele === ob);
if (i >= 0) {
this.observerList.splice(i, 1);
}
}
//当被观察者本身的数据发生改变时,通知观察者做出相应的改变
notify(){
//通过调用观察者提供的更新接口来实现更新操作
this.obList.forEach((item) => {
item.update(this.data);
})
}
}
//观察者基类
Class Observer{
constructor(id){
this.id = id;
}
update(data){
console.log('observer ' + this.id + ': ' + data + ';');
}
}
//测试观察者模式的函数
function test() {
//创建了一个目标sub 和三个观察者obj1,obj2,obj3
let sub = new Subject('test');
let ob1 = new Observer(1);
let ob2 = new Observer(2);
let ob3 = new Observer(3);
//通过add方法实现“观察”的动作
sub.add(ob1, ob2, ob3);
sub.notify();
sub.remove(ob2);
sub.notify();
}
test();
发布订阅模式
1、定义:
2、其中包括的三个定义:发布者、订阅者和调度中心。调度中心其实就是一个中间者。
1)订阅的过程就是建立[发布者:订阅者]的过程;(当订阅者有多个时,就是个list)
2)发布的过程就是如果在观察者模式中就是目标(发布者)直接调用观察者list(订阅者list)的更新接口;
如果在发布订阅模式中就是发布者通过调用调度中心的接口来调用订阅者的更新接口。(即发布者和订阅者不直接接触,而是通过调度中心来间接的接触)
3、发布者和订阅者怎么联系:
首先订阅者向调度中心订阅想要的事件-----通过调度中心的<发布者,[订阅者1,订阅者2,...]>来实现订阅的功能
发布者发布事件到调度中心-----即发布者调用调度中心的发布函数
调度中心调度观察者的更新方法------及调度中心调用观察者的更新方法
4、实现:
发布者:
//发布者基类
class Publisher {
private _data;
private _id;
private channel; //调度中心
//创建一个发布者的实例需要指定发布者id,数据,调度中心
constructor (defaultId, defaultData, defaultChannel: TopicChannel) {
this._id = defaultId;
this._data = defaultData;
this.channel = defaultChannel;
}
get id () {
return this._id;
}
set id (newId) {
this._id = newId;
}
//当发布者发布事件时,会通过调度中心来发布
publish () {
this.channel.publishBaseId(this.id);
}
}
订阅者:
//订阅者
class FruitSubscriber extends Subscriber {
readonly _id;
readonly _publishId;
private channel;
//创建一个订阅者需要订阅者id,调度中心
constructor (id: string, topicChannel: TopicChannel) {
this._id = id;
this.channel = topicChannel;
this.subscribe(this._publishId);
}
//订阅事件
subscribe (topicId: string) {
this.channel.subscribe(topicId, this);
}
//提供的更新接口,调度中心可以调用此接口来实现通知的功能
update (topicData: string) {
console.log('fruit subscriber ' + this._id + ': ' + topicData);
}
}
调度中心:
//调度中心
class TopicChannel {
private publisherMap;
private subscriberMap;
//创建一个调度中心需要两个map:一个是发布者(存放发布者),一个是订阅组合
constructor () {
this.publisherMap = new Map<string, Publisher>();
this.subscriberMap = new Map<string, Array<Subscriber>>();
}
//添加发布者
addPublisher (publisher: Publisher) {
this.publisherMap.set(publisher.id, publisher);
}
//移除发布者
removePublisher (publisher: Publisher) {
this.publisherMap.delete(publisher.id)
}
//订阅操作(Subscriber要订阅id为publisherId的发布者)
subscribe (publisherId: string, subscriber: Subscriber) {
//如果发布者-订阅者的map中有该发布者,那么就直接把该订阅者push进去,否则重新创建一组
if (this.subscriberMap.has(publisherId)) {
this.subscriberMap.get(publisherId).push(subscriber);
} else {
this.subscriberMap.set(publisherId, [subscriber]);
}
}
//发布操作
publishBaseId (publisherId: string) {
//判断发布者map中是否有该发布者,若有,则遍历发布-订阅map中的订阅者,分别执行其提供的更
新接口实现更新操作
if (this.publisherMap.has(publisherId)) {
this.subscriberMap.get(publisherId).forEach((item)=>{
item.update(this.publisherMap.get(publisherId).data);
})
} else {
console.log('There is not the publisher!');
}
}
}
测试实现:
//分别实例化一个调度中心、两个发布者、两个订阅者
let topicChannel = new TopicChannel();
let applePublisher1 = new Publisher('apple publisher1', 'foo apple1',topicChannel);
let applePublisher2 = new Publisher('apple publisher2', 'foo apple2', topicChannel);
let fruitSubscriber1 = new Subscriber('fruit1', topicChannel);
let fruitSubscriber2 = new Subscriber('fruit2', topicChannel);
//向调度中心添加发布者
topicChannel.addPublisher(applePublisher1);
topicChannel.addPublisher(applePublisher2);
//订阅者实现订阅操作
fruitSubscriber1.subscribe('apple publisher1');
fruitSubscriber1.subscribe('apple publisher2');
fruitSubscriber1.subscribe('apple publisher1');
applePublisher1.publish();
applePublisher2.publish();