行为型:发布订阅模式

定义  

  发布订阅模式是基于一个事件(主题)通道,希望接收通知的对象Subscriber(订阅者)通过自定义事件订阅主题,被激活事件的对象 Publisher (发布者)通过发布主题事件的方式通知订阅者 Subscriber (订阅者)对象。

  简单说就是发布者与订阅者通过事件来通信,这里的发布者是之前观察者模式中的被观察者,订阅者是观察者模式中的观察者,他们角色定位是等价的,只不过是不同的叫法。

发布订阅与观察者模式

  平时我们在微博中关注某个大v,这个大v 并不关心我这个订阅者具备什么特征,我只是通过微博这个平台关注了他,他也只是把他要分享的话题通过微博发出来,我和他之间并不存在直接的联系,然后我自动就能看到这个大v发布的消息,这就是发布订阅模式。

  发布订阅者模式与观察者模式类似,但是两者并不完全相同,发布订阅者模式与观察者相比多了一个中间层事件调度中心,用来对发布者发布的信息进行处理,再通知到各个特定的订阅者,大致过程如下图所示

发布者只是发布某事件,无需知道哪些订阅者,订阅者只需要订阅自己感兴趣的事件,无需关注发布者。

发布者完全不用感知订阅者,不用关心它怎么实现回调方法,事件的注册和触发都发生在独立于双方的第三方平台(调度中心)上,发布-订阅模式下,实现了完全地解耦。

通过之前对观察者模式的实现,我们的Subject类中是持有observer对象的,因此并没有实现两个类的完全解耦。通过添加中间层的调度中心类,我么可以将订阅者和发布者完全解耦,两者不再有直接的关联,而是通过调度中心关联起来。下面我们实现一个发布订阅者模式。

传统写法模拟发布订阅模式

按照上面思路,我们需要写下如下三个类,然后事件中心对象是发布者、订阅者之间的桥梁,我们很快写下如下代码:

  1. 发布者 ---- 观察者模式中的【被观察者】
  2. 订阅者 ---- 观察者模式中的【订阅者】
  3. 事件中心 ---- 类似公共的一个平台
 
/*
发布者:发布、注册xxx事件 or 主题
订阅者:自己的行为,取消订阅,订阅
事件中心:注册发布者的某事件、取消注册发布者的某事件、注册订阅者、取消订阅者、发布事件(通知订阅者)
*/
// 发布者
class Pulisher {
constructor (name, evtCenter) {
this.name = name;
this.evtCenter = evtCenter;
}
// 向事件调度中心-注册某事件
register (evtName) {
this.evtCenter.registerEvt(evtName)
}
unregister (evtName) {
this.evtCenter.unRegisterEvt(evtName)
}
// 向事件调度中心-发布某事件
publish (evtName, ...params) {
this.evtCenter.publish(evtName, ...params)
}
}
// 订阅者
class Subscriber {
constructor (name,evtCenter) {
this.name = name;
this.evtCenter = evtCenter;
}
//订阅
subscribe(evtName) {
this.evtCenter.addSubscribe(evtName, this);
}
//取消订阅
unSubscribe(evtName) {
this.evtCenter.unAddSubscribe(evtName, this);
}
//接收
update(params) {
console.log(`我接收到了,${params}`);
}
}
// 事件调度中心
class EvtCenter {
constructor (name) {
this.name = name;
this.evtHandle = {}
}
// 注册发布者要发布的事件
registerEvt (evtName) {
if (!this.evtHandle[evtName]) {
this.evtHandle[evtName] = []
}
}
// 取消注册发布者要发布的事件
unRegisterEvt (evtName) {
delete this.evtHandle[evtName];
}
// 增加订阅者-注册观察者
addSubscribe(evtName, sub) {
if (this.evtHandle[evtName]) {
this.evtHandle[evtName].push(sub);
}
}
// 取消订阅者-移除注册观察者
unAddSubscribe(evtName, sub) {
this.evtHandle[evtName].forEach((item, index) => {
if (item === sub) {
this.evtHandle[evtName].splice(index, 1);
}
});
}
// 事件调度中心-发布某事件
publish (evtName, ...params) {
this.evtHandle[evtName] && this.evtHandle[evtName].forEach((item) => {
item.update(...params);
});
}
}
// 测试
const evtCenter1 = new EvtCenter('报社调度中心1')
const pulisher1 = new Pulisher('报社1', evtCenter1)
const sub1 = new Subscriber('我是sub1, 我对日报感兴趣', evtCenter1)
const sub2 = new Subscriber('我是sub2, 我对日报感兴趣', evtCenter1)
const sub3 = new Subscriber('我是sub3, 我对中报感兴趣', evtCenter1)
const sub4 = new Subscriber('我是sub4, 我对晚报感兴趣', evtCenter1)
// 发布者-注册三个事件到事件中心
pulisher1.register('广州日报')
pulisher1.register('广州中报')
pulisher1.register('广州晚报')
// 订阅者可以自己订阅,当然也可以直接操作事件中心
sub1.subscribe('广州日报')
sub2.subscribe('广州日报')
sub3.subscribe('广州中报')
sub4.subscribe('广州晚报')
// 现在开始发布事件
pulisher1.publish('广州日报', '广州日报')
pulisher1.publish('广州中报', '广州中报')
pulisher1.publish('广州晚报', '广州晚报')
pulisher1.unregister('广州日报')
// 再一次发布事件
console.log('再一次发布事件,这次我取消了日报') // 没有输出广州日报
pulisher1.publish('广州日报', '广州日报')
pulisher1.publish('广州中报', '广州中报')
pulisher1.publish('广州晚报', '广州晚报')

简单写法--面向事件调度中心编程

在js中函数是第一等公民,天生适合回调函数,所以可以直接面向事件调度中心编码即可。我们要做的事情其实就是触发什么事件,执行什么动作。

 
// 事件调度中心
class PubSub {
constructor () {
this.evtHandles = {}
}
// 订阅
subscribe (evtName, callback) {
if (!this.evtHandles[evtName]) {
this.evtHandles[evtName] = [callback];
}
this.evtHandles[evtName].push(callback);
}
// 发布
publish(evtName, ...arg) {
if (this.evtHandles[evtName]) {
for(let fn of this.evtHandles[evtName]) {
fn.call(this, ...arg);
}
}
}
unSubscribe (evtName, fn) { // 取消订阅
let fnList = this.evtHandles[evtName];
if (!fnList) return false;
if (!fn) {
// 不传入指定取消的订阅方法,则清空所有key下的订阅
this.evtHandles[evtName] = []
} else {
fnList.forEach((item, index) => {
if (item === fn) {
fnList.splice(index, 1);
}
})
}
}
}
// 先订阅在发布
const pub1 = new PubSub()
// 订阅三个事件
pub1.subscribe('onWork', time => {
console.log(`上班了:${time}`);
})
pub1.subscribe('onWork', time => {
console.log(`上班了:${time},开始打开待办事项`);
})
pub1.subscribe('onOffWork', time => {
console.log(`下班了:${time}`);
})
pub1.subscribe('onLaunch', time => {
console.log(`吃饭了:${time}`);
})
// 发布对应的事件
pub1.publish('onWork', '09:00:00');
pub1.publish('onLaunch', '12:00:00');
pub1.publish('onOffWork', '18:00:00');
// 取消onWork 事件
pub1.unSubscribe('onWork');
// 取消订阅
pub1.unSubscribe('onWork');
console.log(`取消 onWork`);
pub1.publish('onWork', '09:00:00'); // 不会执行

小结

  1. 发布者不直接触及到订阅者、而是由统一的第三方来完成实际的通信的操作,叫做发布订阅模式
  2. 发布者(被观察者)直接操作订阅者的操作,叫做观察者模式
  3. 发布订阅模式,发布者完全不用感知订阅者,不用关心它怎么实现回调方法,事件的注册和触发都发生在独立于双方的第三方平台(事件调度中心)上,发布-订阅模式下,实现了完全地解耦。
  4. 发布订阅核心通过事件来通信,在调度中心中派发给具体的订阅者。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/51676.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI 绘画Stable Diffusion 研究(二)sd模型ControlNet1.1 介绍与安装

部署包作者:秋葉aaaki 免责声明: 本安装包及启动器免费提供 无任何盈利目的 大家好,我是风雨无阻。 众所周知,StableDiffusion 是非常强大的AI绘图工具,需要详细了解StableDiffusion的朋友,可查看我之前的这篇文章: …

深度学习实践——模型部署优化实践

系列实验 深度学习实践——卷积神经网络实践:裂缝识别 深度学习实践——循环神经网络实践 深度学习实践——模型部署优化实践 深度学习实践——模型推理优化练习 源码: 1. 对应的github地址 https://github.com/Asionm/streamlit_demo 2. 对应的gitee地…

fwrite函数

1、函数声明 size_t fwrite( const void *buffer, size_t size, size_t count, FILE *stream ); 2、参数说明 buffer 指向要写入的数据的指针。 size 项大小(以字节为单位)。 count 要写入的项的最大数量。 stream 指向 FILE 结构的指针。 3、…

【机器学习】Cost Function

Cost Function 1、计算 cost2、cost 函数的直观理解3、cost 可视化总结附录 首先,导入所需的库: import numpy as np %matplotlib widget import matplotlib.pyplot as plt from lab_utils_uni import plt_intuition, plt_stationary, plt_update_onclic…

C# VS2022+WinForm+Oracle19.3+存储过程,SQL和代码分离

【我的目的】:SQL和代码分别存放在不同的地方,便于随时修改SQL的内容,也便于修改SQL的书写格式 方案1:把SQL存放在DataSet.xsd中实现SQL和代码分离 方案2:用存储过程实现SQL和代码分离 我最倾向方案1,利用…

网络安全(黑客)自学误区

前言 网络安全是当今社会中至关重要的议题。随着科技的迅猛发展,网络已经渗透到我们生活的方方面面,给我们带来了巨大的便利和机遇。然而,网络也存在着各种风险和威胁,如黑客攻击、数据泄露等。因此,学习网络安全知识…

给你一个项目,你将如何开展性能测试工作?

一、性能三连问 1、何时进行性能测试? 性能测试的工作是基于系统功能已经完备或者已经趋于完备之上的,在功能还不够完备的情况下没有多大的意义。因为后期功能完善上会对系统的性能有影响,过早进入性能测试会出现测试结果不准确、浪费测试资…

一文学会redis在springBoot中的使用

“收藏从未停止,练习从未开始”,或许有那么一些好题好方法,在被你选中收藏后却遗忘在收藏夹里积起了灰?今天请务必打开你沉甸甸的收藏重新回顾,分享一下那些曾让你拍案叫绝的好东西吧! 一、什么是redis缓存…

万年历【小游戏】(Java课设)

系统类型 Java实现的小游戏 使用范围 适合作为Java课设!!! 部署环境 jdk1.8Idea或eclipse 运行效果 更多Java课设系统源码地址:更多Java课设系统源码地址 更多Java小游戏运行效果展示:更多Java小游戏运行效果展…

JavaScript学习 -- 对称加密算法DES

在现代的互联网时代,数据安全性备受关注。为了保护敏感数据的机密性,对称加密算法是一种常用的方法。在JavaScript中,DES(Data Encryption Standard)是一种常用的对称加密算法。本篇博客将为您展示如何在JavaScript中使…

Unity 四元素

//-------------旋转------------ // //设置角度 (超过90或负数时,会设置-1结果是359这样的问题,可以使用下面旋转的方式) transform.rotate new Quaternion(0,0,0,0);//Quaternion四元数 transform.localEulerAngles new Vector3(0,0,0);//EulerA…

企业电子招投标采购系统源码之-java spring cloud+spring boot

​ 信息数智化招采系统 服务框架:Spring Cloud、Spring Boot2、Mybatis、OAuth2、Security 前端架构:VUE、Uniapp、Layui、Bootstrap、H5、CSS3 涉及技术:Eureka、Config、Zuul、OAuth2、Security、OSS、Turbine、Zipkin、Feign、Monitor、…

Restful的详细介绍~

RESTFUL简介: Restful是我们看待服务器的一种方式,我们都知道Java一切皆对象,因此在Java中,我们可以将所有的内容都看成对象,而在这里,RESTFUL是我们看待服务器的一种方式,我们可将服务器中的所…

idea中设置maven本地仓库和自动下载依赖jar包

1.下载maven 地址&#xff1a;maven3.6.3 解压缩在D:\apache-maven-3.6.3-bin\apache-maven-3.6.3\目录下新建文件夹repository打开apache-maven-3.6.3-bin\apache-maven-3.6.3\conf文件中的settings.xml编辑&#xff1a;新增本地仓库路径 <localRepository>D:\apache-…

ChatGPT与高等教育变革:价值、影响及未来发展

最近一段时间&#xff0c;ChatGPT吸引了社会各界的目光&#xff0c;它可以撰写会议通知、新闻稿、新年贺信&#xff0c;还可以作诗、写文章&#xff0c;甚至可以撰写学术论文。比尔盖茨、马斯克等知名人物纷纷为此发声&#xff0c;谷歌、百度等知名企业纷纷宣布要提供类似产品。…

WIZnet W5100S-EVB-Pico DHCP 配置教程(三)

DHCP协议介绍 什么是DHCP&#xff1f; 动态主机配置协议DHCP&#xff08;Dynamic Host Configuration Protocol&#xff09;是一种网络管理协议&#xff0c;用于集中对用户IP地址进行动态管理和配置。 DHCP于1993年10月成为标准协议&#xff0c;其前身是BOOTP协议。DHCP协议由…

ceph集群中RBD的性能测试、性能调优

文章目录 rados benchrbd bench-write测试工具Fio测试ceph rbd块设备的iops性能测试ceph rbd块设备的带宽测试ceph rbd块设备的延迟 性能调优 rados bench 参考&#xff1a;https://blog.csdn.net/Micha_Lu/article/details/126490260 rados bench为ceph自带的基准测试工具&am…

常见网关对比

常见网关对比 目前常见的开源网关大致上按照语言分类有如下几类&#xff1a; Nginxlua &#xff1a;OpenResty、Kong、Orange、Abtesting gateway 等 Java &#xff1a;Zuul/Zuul2、Spring Cloud Gateway、Kaazing KWG、gravitee、Dromara soul 等 Go &#xff1a;Janus、fa…

多线程只需这一篇足够

开玩笑的 本篇详细讲述了多线程的各种细节及操作方法 对锁的各种操作&#xff0c;以及原子性的阐述 原谅我嚣张的标题 Begin&#xff1a;本篇文章尽可能详细的讲述了线程的概念、使用、安全问题&#xff0c;以及消费者生产者模型的设计理念和实现代码。对于单例模式的两种实现代…

spring6——容器

文章目录 容器&#xff1a;IocIoc容器控制反转&#xff08;Ioc&#xff09;依赖注入IoC容器在Spring的实现 基于XML管理Bean搭建环境获取bean依赖注入setter注入构造器注入特殊值处理字面量赋值null值xml实体CDATA节 特殊类型属性注入为对象类型属性赋值方式一&#xff1a;引入…