发布订阅模式

零、目录

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

 一、应用场景

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

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

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

二、实现原理

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

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

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

三、代码实现

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

/*
    以下代码模拟场景:
        用户们订阅了某个博主的博客, 并设置了自己想要的推送方式,当博主发布了博客, 
将数据推送给订阅者们。
*/
"use strict"
function Obesever() {
  let client = [], // 存储用户提供的联系接口
  publish, 
  subscribe
  publish = 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,
  subscribe

  subscribe = 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实现的底层代码等等, 熟练的掌握设计模式, 可以帮助我们更好的深入框架、底层代码的学习。

  • 9
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值