发布订阅模式

article/2025/10/27 3:28:29

零、目录

  1. 应用场景
  2. 实现原理
  3. 代码实现
  4. 全局模式下的订阅发布模式(泛化的订阅发布模式)
  5. 总结

 一、应用场景

​    发布订阅模式,广泛的存在于在我们的生活之中。

​    举个一个简单的例子来说,当我们在浏览视频或者博客论坛之类的网站时,遇到感兴趣的up主或者博主, 我们通常会选择去订阅他们的频道或者内容。 这样一来,每当他们发布一个新的内容, 网站平台方就会通过某种渠道来通知我们, 我们便可以在第一时间了解到这一讯息, 至于是否选择第一时间阅读, 则却决于我们自己,而这就是一个典型的订阅发布模式。

​    反过来思考一下, 倘若不使用发布订阅模式, 当我们想了要解某一些特定信息时, 就需要自己定期的去访问信息平台,查看对方是否更新了新的内容。而这样一来,就会产生一些不必要的时间开支。(定期的访问信息源, 查看是否发生更新事件, 这种方式也称为 轮询 

二、实现原理

​    在第一节应用场景介绍中, 我们可以从中抽离出两个角色 订阅者 subscriber(访问网站平台的用户), 发布者 publisher (发布讯息的用户), 他们之间的关系如图 2.1 所示

​                                                     (2.1 一个基础的订阅发布者模型)

​    结合 图2.1 简单的来说, 发布者 会提供给所有用户一个公开的接口 subscribe,用户们可以通过这个接口提交一些注册信息(比如,一个将数据推送给自己的接口),进而将自己注册成为一个订阅者,同时 发布者 会维护一组数据, 用于保存一组可以联系  订阅者 的接口, 每当 发布者 发布新的内容时, 就可以将这一消息, 通过这组接口数据推送给所有订阅过该内容的 订阅者。(图中的 publish 接口, 实际就是通过遍历订阅者们队列来推送数据的一个方法)

三、代码实现

​    以下我们就用 JavaScript, 简单的模拟实现一下一个 发布订阅模式

/*以下代码模拟场景:用户们订阅了某个博主的博客, 并设置了自己想要的推送方式,当博主发布了博客, 
将数据推送给订阅者们。
*/
"use strict"
function Obesever() {let client = [], // 存储用户提供的联系接口publish, subscribepublish = function(...args) {for(let i = -1;client[++i];) {client[i](args)}}subscribe = function(fn) {/***  fn: 如何处理获取到的订阅内容*/return client.push(fn) // 返回订阅者下标, 用于后续退订需求}return {publish,subscribe}
}let remiliko = Obesever()
// A 用户订阅了 
remiliko.subscribe(function(data) {console.log('remiliko send', data, "to A")
})
// B 用户订阅了
remiliko.subscribe(function(data) {console.log('remiliko send', data, "to B")
})
// remiliko 发布了一些数据, 然后通过接口将数据推送给订阅者
remiliko.publish("设计模式") 
/* 输出结果 *///remiliko send [ '设计模式' ] to A//remiliko send [ '设计模式' ] to B

四、 全局模式下的发布订阅模式(泛化的发布定订阅模式)

​    在 第三节, 我们简单的实现了一个发布订阅模式, 但是代码存在了一定程度的 耦合(这些耦合在一些应用场景是可以被忽视的,比如对于 up主、博主 和 订阅者这种 单一映射 的需求模型之中,这种简单的发布订阅模型 就可以满足需求了),因为 发布者 本身,以及用户所订阅的事件都被约束在了固定的代码块里。

此时,我们不妨考虑以下这么一个应用场景:

​    用户会有一些需求,这些需求不再是针对于某一个特定对象(如关注某一个特定的用户), 而是想要获取满足一组规则的事物,在这一情况下,用户仅仅想要获取满足他们需求的讯息, 同时并不在乎这些讯息究竟是来自于哪一个用户。

​    更具体一点,比如用户有租房需求,某一个用户想要租价格在3000+以内一个月的房子(尽可能简化问题, 因为此处主要介绍发布订阅模式),而此时租房的app之中并没有搜索到满足他意愿的房源, 那么他希望能有一个订阅的功能, 每当出现新的满足 3000+一个月的房子讯息时, 就将这些信息推送给他。 对于这样一个需求而言, 如果维持 第三节 的模型, 那么用户可能需要通过订阅很多的房东来实现, 并且很有可能收到一些不符合需求的信息,从实际开发和生活中的角度来说,这是不合理的。

​    为了解决这个问题, 我们就需要引入一个新的角色比如  中间方, 由它来维护订阅者队列,至此 发布者 和 订阅者 之间并不再直接进行交互,而是通过平台方代为管理,整个执行流程也就从图 2.1 变为 图 4.1 所示。

(4.1 全局模式下的发布订阅发布 泛化 的数据流动图)

​    如 图4.1 所示, 我们从 发布者 publisher 身上将 订阅者队列 提取了出来,生成了一个 中间方 eventBus, 同时根据订阅的内容不同将订阅者队列进行了散列,这是为了实现更加多样化的 发布订阅者 模式,用户可以订阅想要的讯息, 而不再需要关注讯息来自于哪个用户, 而对于 发布者   来说只需要将他们想要推送的信息通过 eventBus 推送出去即可,而且也不再去需要维护一组订阅者数据了。

同样地, 我们也用 JavaScript 代码简单地实现一下

"use strict"
let eventBus = (function() {let event = new Map(),publish,subscribesubscribe = function(eventName, fn) {/*** eventName 订阅的事件名称* fn: 对获取到的数据的处理函数*/let fns = event.get(eventName)if(!fns) {fns = []fns.push(fn)return event.set(eventName, fns) // 此处的 return 用于提前跳出函数调用栈}return fns.push(fn)}publish = function(...args) {/* 由于发布的数据长度不确定, 因此利用 argument的特性 */let eventName = args.shift(),fns = event.get(eventName)for(let i = -1;fns[++i];) {fns[i](args)}}return {publish,subscribe}
})()
// 实际上是需要用一个命中函数来判别, 但为了简化问题, 不多处理
// 模拟客户A 订阅 3000以内的租房需求
eventBus.subscribe('3000', function(data) {console.log(data)
})
// 模拟发布方A, 发送 3000以内
eventBus.publish('3000', "地铁站旁, 月2600 联系方式:xxxxxxxx")
//  模拟发布方B, 发送 3000以内
eventBus.publish('3000', "郊区, 月1600 联系方式:xxxxxxxx")
//[ '地铁站旁, 月2600 联系方式:xxxxxxxx' ]
//[ '郊区, 月1600 联系方式:xxxxxxxx' ]

​    在这种设计模式下,订阅方发布者 双方都是通过 eventBus 进行间接接触的, 这样的好处就是, 双方不必优先了解对方的详细信息, 而是直接将自己的需求(推送内容)推入 eventBus 这个管道, 让 evnetBus 去分发数据。

五、 总结

​    /* *个人总结, 并不具有代表性,只作分享开发心得* , 上述代码还有许多需要优化的点, 比如离线推送, 退订以及一些代码逻辑的优化, 但在此处主要是为了阐述模型实现, 并不多展开*/

​    发布订阅模式, 可以根据订阅载体的不同, 设置出两种不同风格的订阅发布方式。

  • 具体的订阅对象(订阅方是具体的事物或者人),我们可以直接在订阅方的实例对象上去维护映射关系
  • 抽象的订阅对象(订阅方是一组规则或者说是条件约束), 可以引入一个全局的事件管理器代为管理

​    发布订阅模式JavaScript 开发中随处可见它的身影, 比如按钮点击事件, 以及Promise实现的底层代码等等, 熟练的掌握设计模式, 可以帮助我们更好的深入框架、底层代码的学习。


http://chatgpt.dhexx.cn/article/pWrA4XYh.shtml

相关文章

.NET(C#、VB)APP开发——Smobiler平台控件介绍:VLCPlayer

本文简述如何在Smobiler中使用VLCPlayer插件,该插件支持播放rtsp流。 Step 1. 新建一个SmobilerForm窗体,再拖入VLCPlay,布局如下 在设计器中给VLCPlayer.Url赋值或者在窗体的Load事件中赋值 演示使用的rtsp流地址 rtsp://wowzaec2demo.strea…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:TTS

本文简述如何在Smobiler中使用TTS文字转语音。 Step 1. 新建一个SmobilerForm窗体,并在窗体中加入TTS和Button,布局如下 Button的点击事件代码: private void button1_Press(object sender, EventArgs e){ //第一个参数为文本;第…

Smobiler 仿得到APP个人主页

原型如下: 完整代码参考 https://github.com/comsmobiler/BlogsCode/blob/master/Source/BlogsCode_SmobilerForm/MyForm/dedao.cs 思路 可以将原型按照上图分成2个部分,部分A可以使用label、image、button、imagebutton、fontIcon控件来实现&#xff…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:PDFView

本文简述如何在Smobiler中使用PDFView。 Step 1. 新建一个SmobilerForm窗体,再拖入PDfView,布局如下 PDFView.ResourcrPath默认Document,指项目下\Resources\Document,若是pdf文件放在该文件夹下,则在设计器中直接赋值…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:OCR组件

本文简述如何在Smobiler中使用OCR组件进行文字识别。 Step 1. 新建一个SmobilerForm窗体,并在窗体中加入OCR和Button,布局如下 Button的点击事件代码: private void button1_Press(object sender, EventArgs e){ocr1.Recognize((obj,args)>…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:ArcFace人脸识别

本文简述如何在Smobiler中使用ArcFace(虹软人脸识别)。 Step 1. 新建一个SmobilerForm窗体,再拖入Button,Label,TextBox和AcrFace,布局如下 在设计器中给MediaView.Url赋值或者在窗体的Load事件中赋值 Button的事件代码如下 string message …

移动OA办公——Smobiler第一个开源应用解决方案,快来get吧

产品简介 SmoONE是一款移动OA类的开源解决方案,通过Smobiler平台开发,包含了注册、登陆、用户信息等基本功能。集成了OA中使用场景较多的报销、请假、部门管理、成本中心等核心功能。 免费获取方案 开源代码:https://github.com/comsmobile…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:UsbSerial串口通讯组件

本文简述如何在Smobiler中使用UsbSerial。 Step 1. 新建一个SmobilerForm窗体,再拖入UsbSerial和Button,布局如下 按钮事件代码: //连接private void button1_Press_2(object sender, EventArgs e){usbSerial1.Connect(Smobiler.Plugins.USBS…

Smobiler实现手机弹窗

前言 在实际项目中有很多场景需要用到弹窗,如图1 那么这些弹窗在Smobiler中如何实现呢? 正文 Smobiler实现弹窗有两种方式:1.MessageBox.Show 2.ShowDialog和ShowContextDialog。前者适合简易弹窗,后者适合自定义弹窗。 Messa…

Smobiler实现美观登录界面——C# 或.NET Smobiler实例开发手机app(二)

目录 一、 本文目标 二、 准备工作 1、 数据库 2、 材料 三、 界面布局 1、设置控件的属性值 (1) 输入框 (2) 图片属性 (3) HandElectricity的标题的label属性 (4)登录按钮…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:MediaView

本文简述如何在Smobiler中使用MediaView。 Step 1. 新建一个SmobilerForm窗体,再拖入MediaView,MediaView.Size设置(300,225),布局如下 在设计器中给MediaView.Url赋值或者在窗体的Load事件中赋值 播放本地视频可以通过GetResourc…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:AliPay组件

本文简述如何在Smobiler中调用支付宝支付。 Step 1. 界面 新建一个窗体,并在窗体中拖入Button,Label,AliPay等控件,布局如下: Step 2. 代码 在窗体中声明变量 //订单编号private string tradeNo;//支付宝应用编号&am…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:BarcodeReader组件

本文简述如何在Smobiler中使用BarcodeReader组件进行条码识别。Barcodereader通过机器学习能识别不规则条码,效率更好。 Step 1. 新建一个SmobilerForm窗体,并在窗体中加入Barcodereader和Button,布局如下 Button的点击事件代码: …

.NET(C#、VB)APP开发——Smobiler平台控件介绍:LiveStream和LiveStreamPlayer

本文简述如何在Smobiler中使用LiveStream和LiveStreamPlayer。 LiveStream 直播推送插件 Step 1. 新建一个SmobilerForm窗体,并在窗体中加入LiveStream和Button,布局如下 选中LisvStream,在设计器中设置Url(需要事先准备一个视频…

【转载】smobiler说明

类似开发WinForm的方式,使用C#开发Android和IOS的移动应用?听起来感觉不可思议,那么Smobiler平台到底是如何实现的呢,这里给大家介绍一下。 客户端 Smobiler分为两种客户端,一种是开发版,一种是打包版 开发…

.NET(C#)能开发出什么样的APP?盘点那些通过Smobiler开发的移动应用

.NET程序员一定最熟悉所见即所得式开发,熟悉的Visual Studio开发界面,熟悉的C#代码。 Smobiler也是因为具备这样的特性,使开发人员,可以在VisualStudio上,像开发WinForm一样拖拉控件,让许多人在开发APP时,再次回到所见即所得的开发方式中去。 Smobiler的快速开发,让Ama…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:MapView MaptrimView

本文简述如何在Smobiler中使用MapView和MaptrimView。 Mapview MapView 地图插件,可用于显示指定地点地图,显示轨迹等。 Step 1. 新建一个SmobilerForm窗体,再拖入MapView和Button,MapView.Size设置(300,300&#xf…

Smobiler 窗体

在Smobiler开发过程中,大家经常会对窗体的跳转,显示,关闭,生命周期存在一些不明白的地方,这篇文章主要用来说明Smobiler窗体。 Smobiler Form 和WindowsForm编程一样,在手机上显示的界面在Smobiler就是一个…

Smobiler快手小程序开发指南

注:快手小程序审核规范中写明拒绝纯webview小程序, 即无法通过以下步骤上架快手小程序, Smobiler只能作为快手小程序开发的一个补充, 具体见 快手-小程序审核规范 Step.1 注册快手开发者平台 ,登录之后点击创建 创建完成之后再点击应用进入 点击填写&am…

.NET(C#、VB)APP开发——Smobiler平台控件介绍:Bluetooth组件

本文简述如何在Smobiler中使用Bluetooth。 Step 1. 新建一个SmobilerForm窗体&#xff0c;并在窗体中加入Button和Bluetooth&#xff0c;布局如下 Button的点击事件代码&#xff1a; /// <summary>/// 关闭蓝牙/// </summary>/// <param name"sender"…