观察者模式与消息订阅与发布模式
前言:
js中有许多设计模式 比如 工厂模式,适配器模式,代理模式,迭代器模式等等
我这里讲的是观察者模式与消息订阅与发布模式 主要是这些天自己在弄vue的响应式原理
然后我发现如果想要深入了解他 必须具备一些基础知识 不然看了就是一头雾水 这里就讲一个消息订阅与发布模式和观察者模式 他们在vue的响应式原理中有用到 主要是vue2.x
为了方便理解 这边会采用例子进行讲解
例子:
城东的前端大桥桥头有自发行程大型的劳动力市场,每天早上人们聚集在哪里,然后会有老板来这边招工,然后大家就进入去打工,但是好活并不多,每天好活就那么几个,于是想要好活的人们如同过江之鲫,于是滋生了一群黄牛,恶意出售好活,相当于转手了,打乱了原有的市场秩序。于是为了整顿秩序,老板们组成了一个联盟,就提出了业务和订阅功能。
观察者模式
业务订阅的大致功能是这样的: 老板联盟推出五星任务订阅功能,工人们通过购买获得订阅权限,老板发布业务的时候,这些业务有好有坏,会通知拥有订阅权限的弟子。
那么这个事情就存在两个主体
一个是老板,一个是广大工人们
老板联盟要做的事情:
1.他们需要知道那些人购买了业务订阅 也就是要维护这些人的数据
2.他们要及时的把一些有的业务通知有订阅权限的工人
3.他们还要继续提供一个窗口运行新的工人购买订阅权限
广大工人需做的事情:
1.则需要对老板联盟发的业务进行甄别,因为他们不止发好业务也会发差业务
这里面老板联盟实际上就是一个主体,而广大工人就是多个主体
当对象之间存在一对多的依赖关系时,其中一个对象的状态发生改变,所有依赖它的对象都会收到通知,这就是观察者模式。
在观察者模式中,只有两种主体:目标对象 (Object) 和 观察者 (Observer)。老板联盟就是目标对象,工人们就是观察者。
- 目标对象 Subject:
- 维护观察者列表 observerList ———— 维护拥有订阅权限的工人列表
- 定义添加观察者的方法 ———— 提供工人购买订阅权限的功能
- 当自身发生变化后,通过调用自己的 notify 方法依次通知每个观察者执行 update 方法 ———— 发布对应任务后通知有订阅权限的工人
- 观察者 Observer 需要实现 update 方法,供目标对象调用。update方法中可以执行自定义的业务逻辑 ———— 工人们需要定义接收任务通知后的方法,例如去抢任务或任务不适合,继续等待下一个任务
-
class Observer { constructor(name) { this.name = name; } update({taskType, taskInfo}) { // 假设任务分为普通route和及好war if (taskType === "route") { console.log(`${this.name}不需要日常任务`); return; } this.goToTaskHome(taskInfo); } goToTaskHome(info) { console.log(`${this.name}去老板联盟抢${info}任务`); } } class Subject { constructor() { this.observerList = [] } addObserver(observer) { this.observerList.push(observer); } notify(task) { console.log("发布好的业务"); this.observerList.forEach(observer => observer.update(task)) } } const subject = new Subject(); const stu1 = new Observer("工人1"); const stu2 = new Observer("工人2"); // stu1 stu2 购买好的业务通知权限 subject.addObserver(stu1); subject.addObserver(stu2); // 任务殿发布五星任务 const warTask = { taskType: 'war', taskInfo: "能吹空调,轻松的,高工资的" } // 任务大殿通知购买权限弟子 subject.notify(warTask); // 任务殿发布垃圾任务 const routeTask = { taskType: 'route', taskInfo: "大太阳地下晒的" } subject.notify(routeTask);
输出结果:
// 战斗任务
发布五星任务
工人1去老板联盟抢能吹空调,轻松的,高工资的任务
工人2去老板联盟抢能吹空调,轻松的,高工资的任务// 日常任务
发布五星任务
工人1不需要大太阳地下晒的的任务
工人2不需要大太阳地下晒的的任务
通过上面代码我们可以看到,当老板联盟发布任务后,订阅的工人(观察者们)都会收到任务最新通知。
看到这里,不知道你可以理解观察者模式
通过上面代码我们可以看到,当老板联盟发布任务后,订阅的工人(观察者们)都会收到任务最新通知。
看到这里,不知道你可以理解观察者模式
总的来说 这个应该分观察者类和被观察类
观察者类内部要有队订阅的任务进行接收的函数 以便于调用
被观察者内部要有一个数组用于维护观察者 同时提供方法增加 按道理也应该有一个删 如果是复杂的还应该做到增删查改以面对需求 最后也是最重要的就是把任务通过传参的方式把任务传递给观察者也就是调用观察者里面接收任务的函数
再举个例子:
比如你要应聘字节跳动的前端工程师,结果字节跳动HR 告诉你没坑位了,留下你的电话,等有坑位联系你。于是,你美滋滋的留下了联系方式。殊不知,HR 已经留下了好多联系方式。好在 2022 年 8 月 1 号那天,字节跳动有了前端工程师的坑位,HR 挨着给留下的联系方式联系了一通。
案例中字节跳动就是目标对象 Subject ,联系方式列表就是用来维护观察者的 observerList ,根据前端职位的有无来调用 notify 方法。
发布订阅模式
我们还是紧接着上面的例子,老板联盟发现了 自己维护那么一些东西实在是太麻烦了,于是这个时候中介就出现了,他们只要老板联盟和之前一样发布新的工作就好,而且维护的工作也不需要老板联盟复杂,照样可以提供廉价的工人劳动力,老板乐开了花,天底下还有这种好事!
那什么是发布订阅模式呐? 基于一个事件(主题)通道,希望接收通知的对象 Subscriber 通过自定义事件订阅主题,被激活事件的对象 Publisher 通过发布主题事件的方式通知各个订阅该主题的 Subscriber 对象。
因此发布订阅模式与观察者模式相比,发布订阅模式中有三个角色,发布者 Publisher(老板们) ,事件调度中心 Event Channel(中介),订阅者 Subscriber(广大工人)
我再据举一个例子,如果我同时追更多部动漫,电视剧,小说等等 但是因为目前国内平台割据数据不互通 我可不想这边这一个客户端 那边再下一个客户端 麻烦死了不说 每天还推送一堆垃圾东西给我,同时我要求检查更新还需要一个一个点开,那么我自己写一个软件,他可以收集每一个网站的内容,每当哪一个更新之后我就打开他们去看最新的章节。
那么在这里每一个内容网站 就是发布者Publisher 追剧人就是订阅者 Subscribe,而软件则承担了事件通道 Event Channel 功能
<script>
class PubSub{
constructor()
{
//中介
this.events = {}
}
//订阅
subscribe(type,cd)
{
console.log(type);
console.log(cd);
if(!this.events[type])
{
this.events[type] = []
}
this.events[type].push(cd)
}
//发布
publish(type,...args)
{
console.log(type);
console.log(...args);
if(this.events[type])
{
//事件发布时,订阅的回调函数全部执行
this.events[type].forEach(function(cd)
{
cd(...args)
})
}
}
//取消订阅
unsubscribe(type,cd)
{
if(this.events[type])
{
//找到哪个要删除回调函数的索引存在就返回索引 不存在就返回-1
const cbIndex = this.events[type].findIndex(function(e)
{
e === cd
})
//表示存在
if(cdIndex != -1)
{
// 使用splice方法删除哪个回调
this.events[type].splice(cdIndex,1)
}
}
// 如果已经不存在任务了就把整个生成的订阅去掉
if(this.events[type].length === 0)
{
delete this.events[type]
}
}
//全部取消
unsubscribeAll(type)
{
// 直接删 管你是谁
if(this.events[type])
{
delete this.events[type]
}
}
}
let pubsub = new PubSub()
// 工人一订阅五星任务
pubsub.subscribe('warTask', function (taskInfo){
console.log("工人一只接5星任务老板们发布五星任务,任务信息:" + taskInfo);
})
// 工人一订阅日常任务
pubsub.subscribe('routeTask', function (taskInfo) {
console.log("工人二只接日常任务老板们发布日常任务,任务信息:" + taskInfo);
});
// 工人三订阅全类型任务
pubsub.subscribe('allTask', function (taskInfo) {
console.log("工人三 啥我都接老板们发布任务,任务信息:" + taskInfo);
});
// 发布战斗任务
pubsub.publish('warTask', "五星好任务");
pubsub.publish('allTask', "五星好任务");
// 发布日常任务
pubsub.publish('routeTask', "垃圾业务");
pubsub.publish('allTask', "垃圾业务");
</script>
输出结果
老板们发布五星任务,任务信息:五星好任务
老板们发布五星任务,任务信息:五星好任务
老板们发布日常任务,任务信息:垃圾业务
老板们发布五星任务,任务信息:垃圾业务
这我感觉是把上方的观察者模式换一个方式的区分。各位看着像什么呢?是不是像监听!
实际上js中的dom事件监听本质是也是事件的消息订阅与发布,只要发布了任务消息,那么就会执行和这个任务消息订阅了的回调函数
这里的时候工人和上方不同不再是一个一个的实体对象由调用实体对象的方法来执行任务 同样的在这里老板们也不再成为一个实体对象了 双方都没有实际上的接触了 但是一旦发布也依旧会立刻执行,输出结果。
通过输出结果,我们可以发现发布者和订阅者不知道对方的存在。需要第三方中介,将订阅者和发布者串联起来,利用中介过滤和分配所有输入的消息。也就是说,发布-订阅模式用来处理不同系统组件的信息交流,即使这些组件不知道对方的存在
这就是他最重要的特点
总结
观察者模式 往往就是主 从 两点一线 角色明确 但是缺点会出现代码的耦合性
而消息的订阅与发布则是中间有一个中转站 往往发布者和订阅者都不知道彼此 只知道中介
优点是:松散耦合,灵活度高,通常应用在异步编程中
但是如果事件变多了,会提供维护的成本
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· Vue3状态管理终极指南:Pinia保姆级教程