[转] [设计模式]订阅发布模式与观察者模式
发布—订阅模式与观察者模式区别
观察者模式:
- 结构: 在观察者模式中,通常包含两个主要角色 — 观察者(Observers)和被观察者(Subject)。
- 关系: 观察者直接订阅被观察者。被观察者维护一个观察者列表,并在状态变化时通知所有观察者。
- 通知方式: 被观察者主动向观察者推送信息,通常是调用观察者的方法来进行通知。
发布-订阅模式:
- 结构: 在发布-订阅模式中,有一个中介者(通常称为“事件总线”或“消息队列”)来管理订阅者和发布者之间的关系。
- 关系: 发布者和订阅者不直接耦合,它们通过中介者进行通信。发布者将消息发送到中介者,然后中介者将消息传递给所有订阅者。
- 通知方式: 订阅者通过向中介者注册感兴趣的事件或主题,中介者在接收到消息后负责将消息分发给所有订阅者。
关键区别:
- 直接耦合 vs 间接耦合: 观察者模式中,观察者和被观察者直接耦合;而发布-订阅模式中,发布者和订阅者通过中介者间接耦合。
- 通信方式: 观察者模式中,通常是被观察者直接通知观察者;发布-订阅模式中,通过中介者进行通信,发布者只需向中介者发布消息,而不需要直接通知订阅者。
观察者模式更适合简单的一对多通信,而发布-订阅模式则更适用于松散耦合的场景,尤其是在复杂系统中需要处理多个事件和消息的情况。
发布订阅模式: 订阅者(Subscriber)把自己想订阅的事件注册(Subscribe)到调度中心(Topic),当发布者(Publisher)发布该事件(Publish topic)到调度中心,也就是该事件触发时,由调度中心统一调度(Fire Event)订阅者注册到调度中心的处理代码。
作用
可以广泛应用于异步编程中
,这是一种替代传递回调函数的方案。
可以取代对象之间硬编码的通知机制
,一个对象不用再显式地调用另外一个对象的某个接口。
从架构上来看,无论是 MVC 还是 MVVM
,都少不了发布—订阅模式的参与。
链接:https://juejin.cn/post/7055441354054172709
故事背景
前端宗门自从发布了传承方案后,宗门日渐繁荣,弟子们的水平不断提高,但新的问题出现了——高质量任务严重不足。宗门任务大殿每个月发布的五星任务是有限的,想要接取五星任务的弟子却如过江之鲫,于是滋生了武侠黄牛,恶意抢任务,坐地起价。
宗门不愿任务被恶意哄抢,决定调整任务市场秩序,因此推出任务订阅功能——观察者模式。
观察者模式
任务订阅的大致功能是这样的: 宗门推出五星任务订阅功能,弟子通过购买获得订阅权限,当宗门发布五星任务后,会通知拥有订阅权限的弟子。
那么任务订阅功能中有两类主体:
- 宗门任务大殿
- 维护拥有订阅权限的弟子列表
- 提供弟子购买订阅权限的功能
- 发布对应任务后通知有订阅权限的弟子
- 接受任务通知的弟子们
上面宗门任务大殿与弟子间的关系其实就构成了一个观察者模式。
那什么是观察者模式那? 当对象之间存在一对多的依赖关系时,其中一个对象的状态发生改变,所有依赖它的对象都会收到通知,这就是观察者模式。
在观察者模式中,只有两种主体:目标对象 (Object
) 和 观察者 (Observer
)。宗门任务大殿就是目标对象,弟子们就是观察者。
- 目标对象
Subject
:- 维护观察者列表
observerList
———— 维护拥有订阅权限的弟子列表 - 定义添加观察者的方法 ———— 提供弟子购买订阅权限的功能
- 当自身发生变化后,通过调用自己的
notify
方法依次通知每个观察者执行update
方法 ———— 发布对应任务后通知有订阅权限的弟子
- 维护观察者列表
- 观察者
Observer
需要实现update
方法,供目标对象调用。update
方法中可以执行自定义的业务逻辑 ———— 弟子们需要定义接收任务通知后的方法,例如去抢任务或任务不适合,继续等待下一个任务
我们把上面的文字形象化一下:

View Code
输出结果:
// 战斗任务 发布五星任务 弟子1去任务大殿抢猎杀时刻任务 弟子2去任务大殿抢猎杀时刻任务通过上面代码我们可以看到,当宗门发布任务后,订阅的弟子(观察者们)都会收到任务最新通知。
看到这里,不知道你可以理解观察者模式了?
小包再给举个栗子: 比如你要应聘阿里巴巴的前端工程师,结果阿里巴巴 HR 告诉你没坑位了,留下你的电话,等有坑位联系你。于是,你美滋滋的留下了联系方式。殊不知,HR 已经留下了好多联系方式。好在 2022 年 2 月 30 号那天,阿里巴巴有了前端工程师的坑位,HR 挨着给留下的联系方式联系了一通。
案例中阿里巴巴就是目标对象 Subject
,联系方式列表就是用来维护观察者的 observerList
,根据前端职位的有无来调用 notify
方法。
发布订阅模式
那什么是发布订阅模式呐? 基于一个事件(主题)通道,希望接收通知的对象 Subscriber
通过自定义事件订阅主题,被激活事件的对象 Publisher
通过发布主题事件的方式通知各个订阅该主题的 Subscriber
对象。
因此发布订阅模式与观察者模式相比,发布订阅模式中有三个角色,发布者 Publisher
,事件调度中心 Event Channel
,订阅者 Subscriber
。
上面的文字有些难以理解,我们继续以弟子领取任务为栗子,宗门感觉把任务订阅放在任务大殿中有些繁琐,于是决定在任务大殿和弟子中间添加中介。弟子在中介中订阅其需要的任务类型,当任务大殿发布任务后,中介会将发布任务给对应的订阅者。
- 宗门任务大殿: 任务发布者 ——
Publisher
- 中介功能 ——
Event Channel
- 维护任务类型,以及每种任务下的订阅情况
- 给订阅者提供订阅功能 ——
subscribe
功能 - 当宗门发布任务后,中介会给所有的订阅者发布任务 ——
publish
功能
- 弟子: 任务接受者 ——
Subscriber
以目前的热播剧开端为例,临近过年,摸鱼的心思越来越重,每天就迫不及待的等开端更新,想在开端更新的第一刻就开始看剧,那你会怎么做那?总不能时时刻刻刷新页面吧。平台提供了消息订阅功能,如果你选择订阅,平台更新开端后,会第一时间发消息通知你,订阅后,你就可以愉快的追剧了。
上面案例中,开端就是发布者 Publisher
,追剧人就是订阅者 Subscribe
,平台则承担了事件通道 Event Channel
功能。

class PubSub { constructor() { // 事件中心 // 存储格式: warTask: [], routeTask: [] // 每种事件(任务)下存放其订阅者的回调函数 this.events = {} } // 订阅方法 subscribe(type, cb) { if (!this.events[type]) { this.events[type] = []; } this.events[type].push(cb); } // 发布方法 publish(type, ...args) { if (this.events[type]) { this.events[type].forEach(cb => cb(...args)) } } // 取消订阅方法 unsubscribe(type, cb) { if (this.events[type]) { const cbIndex = this.events[type].findIndex(e=> e === cb) if (cbIndex != -1) { this.events[type].splice(cbIndex, 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("宗门殿发布战斗任务,任务信息:" + 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', "种树浇水");
通过输出结果,我们可以发现发布者和订阅者不知道对方的存在。需要第三方中介,将订阅者和发布者串联起来,利用中介过滤和分配所有输入的消息。也就是说,发布-订阅模式用来处理不同系统组件的信息交流,即使这些组件不知道对方的存在。
总结
上文中提到了观察者模式和发布——订阅模式,我们来总结一下两者差异:
设计模式 | 观察者模式 | 发布订阅模式 |
---|---|---|
主体 | Object观察者、Subject目标对象 | Publisher发布者、Event Channel事件中心、Subscribe订阅者 |
主体关系 | Subject中通过observerList记录ObServer | Publisher和Subscribe不想不知道对方,通过中介联系 |
优点 | 角色明确,Subject和Object要遵循约定的成员方法 | 松散耦合,灵活度高,通常应用在异步编程中 |
缺点 | 紧耦合 | 当事件类型变多时,会增加维护成本 |
使用案例 | 双向数据绑定 | 事件总线EventBus |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
2019-01-17 [原]JSBSim 自动驾驶(浅出)
2017-01-17 FreeSouth的学习osg小贴士
2017-01-17 [osg][原创]osg多屏幕显示,会出现透明需要设置的问题
2017-01-17 [OSG]OSG的相关扩展