前言
本章主要以javascript作为编程语言进行发布-订阅设计模式的个人理解。全文分为3个部分,大概耗时10分钟。
一、发布-订阅模式的介绍
发布-订阅模式,大学所学习的设计模式的课程中,老师并没有讲到这样的一种设计模式。
之所以会知道,是在学习Vue的数据双向绑定的原理时认识的,Vue的作者尤大就是用发布-订阅模式来实现数据的双向绑定的。
那么到底什么是发布-订阅模式?
举个栗子:我在微信上关注了某个公众号,同时其他人也关注了这个公众号,当这个公众号一推出新的推文时,我们每个关注这个公众号的人就都能收到推新信息。在这个事件中,我们就是订阅者,公众号就是发布者,而微信就是我们中间的沟通桥梁、订阅中心。我们只管订阅,公众号只管更新,彼此互不影响,但当公众号一推新时,我们又能立马收到推新信息,这就是发布-订阅模式。
设计模式”(Design Pattern)是针对编程中经常出现的、具有共性的问题,所提出的解决方法。
发布-订阅模式要解决的一类问题,就是关于发布者和订阅者两者之间的通讯解耦问题。通讯指的是发布者一更新,订阅者也会跟着自动更新;而解耦指的是发布者和订阅者之间不用再相互依赖,我们不用每时每刻去问公众号什么时候推新,公众号在推新时也不用去向每个人问有没有订阅,彼此之间一个只管订阅、一个只管发布,互不影响。
二、发布-订阅模式和观察者模式的区别
在设计模式中有一种设计模式叫观察者模式,大概概念如下:
一个或多个观察者对目标的状态感兴趣,通过将自己依附在目标对象上以便注册所感兴趣的内容。
目标状态发生改变并且观察者可能对这些改变感兴趣,会发送一个通知消息,调用每个观察者的更新方法。
当观察者不再对目标状态感兴趣时,他们可以简单将自己从中分离。
从概念上看,观察者模式要解决问题的问题,貌似和发布订阅好像是同一类问题。其实在一开始,是没有发布-订阅模式的,或者说发布-订阅模式,被叫成是观察者模式的一种别称。但其实仔细区分的话,这两者还是有不同点的:
- 在观察者模式中,订阅者是将自己去注册到发布者中的。
- 而在发布-订阅模式中,发布者和订阅者两者间是多了一个订阅中心,两者没有直接关联。如下图所示:
打个比方:
- 观察者模式就好比上课,我们到教室订阅老师的讲课内容,老师当面给我们发布传授他的知识。
- 而发布-订阅模式就是上网课,老师把他要传授的内容录制成网课,发布在网上的某个平台,我们只需要订阅相关的课程就好了。当网课一更新,我们也能立马进行学习。
接下来通过一个应用场景来看他们的不同:
前提假设:我们的页面有多个地方,都需要一个登录异步请求操作获取到数据后,再进行渲染。
问题解析:这个场景可以理解为,当登录完成,数据一更新时,页面元素就跟着做出修改,进行自我的渲染,那用我们的发布-订阅模式和观察者模式分别可以这样写
发布-订阅模式
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style>
#title, #nav {
font-size: 50px;
}
</style>
</head>
<body>
<div id='title'>title:</div>
<div id='nav'>nav:</div>
</body>
<script>
// 定义订阅中心
let subCenter = {
subList: {},
// {login:[cb1,cb2]}
// 添加订阅
add(event, callback) {
if (!this.subList[event]) { // 判断是否有该订阅事件,有的话就push,没有的话就先新建数组再push
this.subList[event] = [];
}
this.subList[event].push(callback);
},
// 发布信息
notice(event) {
var cbList = this.subList[event];
if (!cbList) { // 如果不存在该订阅事件就退出
return;
}
cbList.forEach((cb) => { // 遍历执行该订阅事件下的函数
cb();
});
}
};
// 订阅者添加订阅(订阅login)
subCenter.add('login', () => {
document.getElementById('title').innerHTML = 'title:根据login拿到值了'
});
subCenter.add('login', () => {
document.getElementById('nav').innerHTML = 'nav:根据login拿到值了'
});
// 用定时器模拟异步登录事件,发布者发布事件login
setTimeout(() => {
subCenter.notice('login');
}, 3000)
</script>
</html>
从上面的代码我们可以看到,有一个订阅中心,用于存放订阅者(subList)、添加订阅者(add)和发布信息(notice),订阅者只需要将自己想要订阅的事件(login)添加到订阅中心,然后当发布者发布订阅的事件(login)时,订阅了login的订阅者就都能监听到这个事件(login)的发生,并执行相应的回调。我们再来看下观察者模式:
观察者模式
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
<style>
#title, #nav {
font-size: 50px;
}
</style>
</head>
<body>
<div id='title'>title:</div>
<div id='nav'>nav:</div>
</body>
<script>
// 定义目标对象(login)
function Login(){
this.list = []
//[title,nva]
};
// 添加订阅
Login.prototype.add = function (el){
this.list.push(el);
}
// 发布信息
Login.prototype.notice = function (){
this.list.forEach((el) => { // 遍历执行该订阅事件下的函数
el.update()
});
}
let login = new Login();
// 定义观察者(element)
function Element(el) {
this.el = el
}
Element.prototype.update = function() {
document.getElementById(this.el).innerHTML = this.el + ':根据login拿到值了'
}
let title = new Element('title');
let nav = new Element('nav');
// 观察者将自己订阅进目标对象里
login.add(title);
login.add(nav);
// 用定时器模拟登录异步操作
setTimeout(() => {
login.notice();
}, 3000)
</script>
</html>
我们可以看到,同一个应用场景,上面的代码也是可以实现,但相比发布-订阅模式不同的时,观察者模式并没有一个订阅中心,而是存在着一个目标对象(Login),而且观察者在订阅时,是将自己订阅进目标对象里的(login.add(title)),所以观察者和发布者之间还是存在的一定的联系,并不会像发布-订阅模式一样,完全分离。
总结
发布-订阅模式相比观察者模式,会多出来个订阅中心,作为发布者和订阅者之间的处理桥梁,这样的好处是使两者之间完全解耦,消除了发布者和订阅者之间的依赖。
三、发布-订阅模式关于Vue数据双向绑定原理的应用
前面说到,发布-订阅模式是我在学习Vue的数据双向绑定的原理时认识,所以这一小章节就来说一下发布-订阅模式关于Vue数据双向绑定原理的应用。由于篇幅问题,就另起文章写了:
https://blog.csdn.net/weixin_42436131/article/details/99546713