找回密码
 立即注册
首页 业界区 业界 行为型:发布订阅模式

行为型:发布订阅模式

韶侪 2025-6-6 09:40:19
定义  

  发布订阅模式是基于一个事件(主题)通道,希望接收通知的对象Subscriber (订阅者)通过自定义事件订阅主题,被激活事件的对象 Publisher (发布者)通过发布主题事件的方式通知订阅者 Subscriber (订阅者)对象。
  简单说就是发布者与订阅者通过事件来通信,这里的发布者是之前观察者模式中的被观察者,订阅者是观察者模式中的观察者,他们角色定位是等价的,只不过是不同的叫法。发布订阅与观察者模式

  平时我们在微博中关注某个大v,这个大v 并不关心我这个订阅者具备什么特征,我只是通过微博这个平台关注了他,他也只是把他要分享的话题通过微博发出来,我和他之间并不存在直接的联系,然后我自动就能看到这个大v发布的消息,这就是发布订阅模式。
  发布订阅者模式与观察者模式类似,但是两者并不完全相同,发布订阅者模式与观察者相比多了一个中间层事件调度中心,用来对发布者发布的信息进行处理,再通知到各个特定的订阅者,大致过程如下图所示
1.png

发布者只是发布某事件,无需知道哪些订阅者,订阅者只需要订阅自己感兴趣的事件,无需关注发布者。
发布者完全不用感知订阅者,不用关心它怎么实现回调方法,事件的注册和触发都发生在独立于双方的第三方平台(调度中心)上,发布-订阅模式下,实现了完全地解耦。
通过之前对观察者模式的实现,我们的Subject类中是持有observer对象的,因此并没有实现两个类的完全解耦。通过添加中间层的调度中心类,我么可以将订阅者和发布者完全解耦,两者不再有直接的关联,而是通过调度中心关联起来。下面我们实现一个发布订阅者模式。
传统写法模拟发布订阅模式

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

  • 发布者 ---- 观察者模式中的【被观察者】
  • 订阅者 ---- 观察者模式中的【订阅者】
  • 事件中心 ---- 类似公共的一个平台
  1. /*
  2. 发布者:发布、注册xxx事件 or 主题
  3. 订阅者:自己的行为,取消订阅,订阅
  4. 事件中心:注册发布者的某事件、取消注册发布者的某事件、注册订阅者、取消订阅者、发布事件(通知订阅者)
  5. */
  6. // 发布者
  7. class Pulisher {
  8.   constructor (name, evtCenter) {
  9.     this.name = name;
  10.     this.evtCenter = evtCenter;
  11.   }
  12.   // 向事件调度中心-注册某事件
  13.   register (evtName) {
  14.     this.evtCenter.registerEvt(evtName)
  15.   }
  16.   unregister (evtName) {
  17.     this.evtCenter.unRegisterEvt(evtName)
  18.   }
  19.   // 向事件调度中心-发布某事件
  20.   publish (evtName, ...params) {
  21.     this.evtCenter.publish(evtName, ...params)
  22.   }
  23. }
  24. // 订阅者
  25. class Subscriber {
  26.   constructor (name,evtCenter) {
  27.     this.name = name;
  28.     this.evtCenter = evtCenter;
  29.   }
  30.   //订阅
  31.   subscribe(evtName) {
  32.     this.evtCenter.addSubscribe(evtName, this);
  33.   }
  34.   //取消订阅
  35.   unSubscribe(evtName) {
  36.     this.evtCenter.unAddSubscribe(evtName, this);
  37.   }
  38.   //接收
  39.   update(params) {
  40.     console.log(`我接收到了,${params}`);
  41.   }
  42. }
  43. // 事件调度中心
  44. class EvtCenter {
  45.   constructor (name) {
  46.     this.name = name;
  47.     this.evtHandle = {}
  48.   }
  49.   // 注册发布者要发布的事件
  50.   registerEvt (evtName) {
  51.     if (!this.evtHandle[evtName]) {
  52.       this.evtHandle[evtName] = []
  53.     }
  54.   }
  55.   // 取消注册发布者要发布的事件
  56.   unRegisterEvt (evtName) {
  57.     delete this.evtHandle[evtName];
  58.   }
  59.   // 增加订阅者-注册观察者
  60.   addSubscribe(evtName, sub) {
  61.     if (this.evtHandle[evtName]) {
  62.       this.evtHandle[evtName].push(sub);
  63.     }
  64.   }
  65.    // 取消订阅者-移除注册观察者
  66.    unAddSubscribe(evtName, sub) {
  67.     this.evtHandle[evtName].forEach((item, index) => {
  68.       if (item === sub) {
  69.         this.evtHandle[evtName].splice(index, 1);
  70.       }
  71.     });
  72.   }
  73.   // 事件调度中心-发布某事件
  74.   publish (evtName, ...params) {
  75.     this.evtHandle[evtName] && this.evtHandle[evtName].forEach((item) => {
  76.       item.update(...params);
  77.     });
  78.   }
  79. }
  80. // 测试
  81. const evtCenter1 = new EvtCenter('报社调度中心1')
  82. const pulisher1 = new Pulisher('报社1', evtCenter1)
  83. const sub1 = new Subscriber('我是sub1, 我对日报感兴趣', evtCenter1)
  84. const sub2 = new Subscriber('我是sub2, 我对日报感兴趣', evtCenter1)
  85. const sub3 = new Subscriber('我是sub3, 我对中报感兴趣', evtCenter1)
  86. const sub4 = new Subscriber('我是sub4, 我对晚报感兴趣', evtCenter1)
  87. // 发布者-注册三个事件到事件中心
  88. pulisher1.register('广州日报')
  89. pulisher1.register('广州中报')
  90. pulisher1.register('广州晚报')
  91. // 订阅者可以自己订阅,当然也可以直接操作事件中心
  92. sub1.subscribe('广州日报')
  93. sub2.subscribe('广州日报')
  94. sub3.subscribe('广州中报')
  95. sub4.subscribe('广州晚报')
  96. // 现在开始发布事件
  97. pulisher1.publish('广州日报', '广州日报')
  98. pulisher1.publish('广州中报', '广州中报')
  99. pulisher1.publish('广州晚报', '广州晚报')
  100. pulisher1.unregister('广州日报')
  101. // 再一次发布事件
  102. console.log('再一次发布事件,这次我取消了日报') // 没有输出广州日报
  103. pulisher1.publish('广州日报', '广州日报')
  104. pulisher1.publish('广州中报', '广州中报')
  105. pulisher1.publish('广州晚报', '广州晚报')
复制代码
简单写法--面向事件调度中心编程

在js中函数是第一等公民,天生适合回调函数,所以可以直接面向事件调度中心编码即可。我们要做的事情其实就是触发什么事件,执行什么动作。
  1. // 事件调度中心
  2. class PubSub  {
  3.   constructor () {
  4.     this.evtHandles = {}
  5.   }
  6.   // 订阅
  7.   subscribe (evtName, callback) {
  8.     if (!this.evtHandles[evtName]) {
  9.       this.evtHandles[evtName] = [callback];
  10.     }
  11.     this.evtHandles[evtName].push(callback);
  12.   }
  13.   // 发布
  14.   publish(evtName, ...arg) {
  15.     if (this.evtHandles[evtName]) {
  16.       for(let fn of this.evtHandles[evtName]) {
  17.         fn.call(this, ...arg);
  18.       }
  19.     }
  20.   }
  21.   unSubscribe (evtName, fn) {     // 取消订阅
  22.     let fnList = this.evtHandles[evtName];
  23.     if (!fnList) return false;
  24.     if (!fn) {
  25.       // 不传入指定取消的订阅方法,则清空所有key下的订阅
  26.       this.evtHandles[evtName] = []
  27.     } else {
  28.       fnList.forEach((item, index) => {
  29.         if (item === fn) {
  30.           fnList.splice(index, 1);
  31.         }
  32.       })
  33.     }
  34.   }
  35. }
  36. // 先订阅在发布
  37. const pub1 = new PubSub()
  38. // 订阅三个事件
  39. pub1.subscribe('onWork', time => {
  40.   console.log(`上班了:${time}`);
  41. })
  42. pub1.subscribe('onWork', time => {
  43.   console.log(`上班了:${time},开始打开待办事项`);
  44. })
  45. pub1.subscribe('onOffWork', time => {
  46.   console.log(`下班了:${time}`);
  47. })
  48. pub1.subscribe('onLaunch', time => {
  49.   console.log(`吃饭了:${time}`);
  50. })
  51. // 发布对应的事件
  52. pub1.publish('onWork', '09:00:00');
  53. pub1.publish('onLaunch', '12:00:00');
  54. pub1.publish('onOffWork', '18:00:00');
  55. // 取消onWork 事件
  56. pub1.unSubscribe('onWork');
  57. // 取消订阅
  58. pub1.unSubscribe('onWork');
  59. console.log(`取消 onWork`);
  60. pub1.publish('onWork', '09:00:00'); // 不会执行
复制代码
小结


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

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
您需要登录后才可以回帖 登录 | 立即注册