引言
老生常谈,当初我们还在广泛使用原生js来开发的时候,总是去阅读一些js的设计模式,在开发中其实总是会不知不觉的就会用到,那么到了现在前端框架流行的时期,同样的,其实在框架的内部也是在使用设计模式来支撑的。那今天咱们就聊聊设计模式之一:发布订阅模式,也算是回顾下经典吧。
概念对比
在这之前,咱们还是先明确下,观察者模式和发布订阅模式的区别,其实这两个模式一直是看似一样的,其实肯定不一样,不然为啥还有两个呢?当然,咱们还是以实际的规范来解释;先上图:
大家看到图应该就一目了然了,是不是想起一句话,没有中间商赚差价......好吧,观察者模式更像是发布者对应接收者,而发布订阅模式是增加了中间管理者,负责分发内容,要说为啥不直接了当对接,其实是解耦了前者和后者的关联;
- 观察者模式:周杰伦开演唱会 - 粉丝
- 发布订阅模式:周杰伦定演唱会信息 - 演唱会承接方 - 粉丝
观察者模式
观察者模式定义了对象之间一对多的依赖关系,当一个对象改变了状态,它的所有依赖会被通知,然后自动更新。熟悉Vue的同学知道,在Vue中通知视图更新使用了"观察者模式",而使用"发布订阅"来实现的eventBus;
//管理者
class regulator {
constructor() {
this.observers = []; // 存储观察者
}
addObserver(observer) {
this.observers.push(observer);
}
removeObserver(observer) {
//判断观察者是否存在,存在即删除
let index = this.observers.findIndex((item) => item === observer);
if (index > -1) {
this.observers.splice(idx, 1);
}
}
notify() {
for (let observer of this.observers) {
observer.update();
}
}
}
比如说周杰伦开演唱会,首先定义管理者类,负责收集存储观察者,添加、删除还有需要告诉观察者信息函数;然后我们需要观察者类,在管理者那里报名,我是您的忠实粉丝,您在哪里开演唱会告诉下我哈~~~
class Observers {
constructor(name) {
this.name = name;
}
// 目标对象更新时触发的回调
update() {
console.log(`周杰伦告诉我要唱歌啦,我是:${this.name}`);
}
}
// 来了两名粉丝
let obs1 = new Observers("粉丝01");
let obs2 = new Observers("粉丝02");
接下来如果周杰伦定了演唱会时间地点等信息,那么就要告诉粉丝,我要唱歌啦;
//小周
let subject = new regulator();
//添加自己的粉丝
subject.addObserver(obs1);
subject.addObserver(obs2);
//发送消息
subject.notify();
//周杰伦告诉我要唱歌啦,我是:粉丝01
//周杰伦告诉我要唱歌啦,我是:粉丝02
哎呦~~不好意思,有一个不是我的粉丝,于是执行 subject.removeObserver(obs2)
,再通知只有粉丝01知道了;
发布订阅模式
那我们回到发布订阅模式来,在这中间增加全局事件总栈(bus),来管理负责需要给那些粉丝通知,在Vue中的EventBus,就像是所有组件共用相同的事件中心,可以向该中心注册发送事件或接收事件,所以组件都可以平行地通知其他组件,但也不能滥用,否则管理不当消息通知也会混乱;那我们其实就是想要搞明白类似这样的全局事件中心是怎样的原理;
有一天小周觉得自己去挨个告诉粉丝同学会不会太累了,要不要找人帮帮忙,有没有这个样的一个控制中心帮我做这件事情那该多好:
- 发布者:Regulator
- 事件总线:Event Channel
- 订阅者:subject
首先我们先定义事件调度中心:
class EventChannel {
// 初始化订阅者对象(key为对象名称,value为回调函数)
subjects = {};
//添加订阅者
on(subscriber, callback) {
if (!this.subjects[subscriber]) {
this.subjects[subscriber] = [];
}
this.subjects[subscriber].push(callback);
}
// 实现取消订阅事件
off(subscriber, callback) {
if (callback === null) {
this.subjects[subscriber] = [];
} else {
const index = this.subjects[subscriber].indexOf(callback);
if(index!=-1){
this.subjects[subscriber].splice(index, 1);
}
}
}
// 实现发布订阅事件
emit(subscriber, data) {
if (this.subjects[subscriber]) {
this.subjects[subscriber].forEach((item) => item(data));
}
}
}
然后定义发布者和定于类信息:
class regulator {
subscriber = "";
data = "";
constructor(subscriber, data) {
this.subscriber = subscriber;
this.data = data;
}
}
class subject {
subscriber: string = "";
constructor(subscriber, callback) {
this.subscriber = subscriber;
this.callback = callback;
}
callback() {}
}
此时,去实例化regulator(周杰伦),并且定义要发布的消息;
const pual = new regulator("push", { message: "jay 发布演唱会消息~" });
const eventBus = new EventChannel();
eventBus.on("push", (data) => {
console.log("来消息了:" + data.message);
});
eventBus.emit(pual.subscriber, pual.data, "push 传的值");
eventBus为实例化事件总栈的对象,此时on方法定义接收指定名称"push"的消息,而emit就是触发消息的人,广播"jay 发布演唱会消息~"
在Vue中可以通过全局挂载EventBus:
// main.js
Vue.prototype.$EventBus = new Vue()
如果说有两个页面,父组件定义需要发送的消息,推送出去:
@click="goSubComponent()"
export default {
methods: {
goSubComponent() {
$EventBu.$emit("pushMsg", 'ParentComponent的消息');
}
}
};
接着就是在child组件接收有消息:
{{msg}}
export default {
data(){
return {
msg: ''
}
},
mounted() {
$EventBu.$on("pushMsg", (msg) => {
this.msg = msg;
});
}
};
思考一下,每次广播的的时候,针对的是表示相同就执行,有时候相同表示的执行推送给一部分,相同表示的有些有获取权限,有些没有,那就加个唯一标识
on(subscriber, callback, id) {
let obj = {};
if (!this.subjects[subscriber]) {
this.subjects[subscriber] = [];
}
obj["fn"] = callback;
obj["id"] = id;
this.subjects[subscriber].push(obj);
}
emit(subscriber, data, id) {
if (this.subjects[subscriber]) {
this.subjects[subscriber].forEach((item) => {
item.id == id && item.fn(data);
});
}
}
这样,会有两层限制关系,找到订阅者,然后看看手里有没有铭牌,有的话通知,没有的话也不会通知到;
总结
话说回来,感觉发布订阅模式其实就是观察者模式的进阶版;
1.观察者模式只是维护单一事件来对应多个对象;
2.发布订阅模式是维护多个事件应对分发对应多个对象;
3.观察者和监听对象强耦合,而发布订阅则是做到了和监听对象的解耦;
其实两者模式区别在于事件管理栈,两者其实都有优缺点,都是一对多的模式,只是在我们日常开发中找到适合自己的模式;