写点什么

简化理解:发布订阅

作者:掘金安东尼
  • 2022 年 7 月 24 日
  • 本文字数:2339 字

    阅读完需:约 8 分钟

简化理解:发布订阅

我们前不久写过发布订阅模式:


// 发布者 Publisherclass Pub {    constructor() {        this.deps = [];    }    addDep(dep) {        this.deps.push(dep);    }    publish(dep) {        this.deps.forEach(item => item === dep && item.notify());    }}// 订阅者 Subscriberclass Sub {    constructor(val) {        this.val = val;    }
update(callback) { callback(this.val) }}
// 调度中心class Dep { constructor(callback) { // 核心是这个 callback 函数; this.subs = []; this.callback = callback; } addSub(sub) { this.subs.push(sub); } notify() { this.subs.forEach(item => item.update(this.callback)); }}
let pub = new Pub() // 实例化一个发布者
// 实例化一个调度中心,传入一个用于处理数据的函数;const dep1 = new Dep((data) => console.log('我是调度中心,我先把消息处理一下,然后发给 ===》》》', data))
let sub1 = new Sub("订阅者1") // 实例化订阅者1let sub2 = new Sub("订阅者2") // 实例化订阅者2
pub.addDep(dep) // 发布者绑定调度中心
dep.addSub(sub1) // 调度中心添加订阅者1dep.addSub(sub2) // 调度中心添加订阅者2
pub.publish(dep) // 发布者把消息推给调度者
// 我是调度中心,我先把消息处理下先 订阅者1// 我是调度中心,我先把消息处理下先 订阅者2
复制代码


但是这样看,似乎有点太复杂了:


  • 发布者需要有两个方法,绑定调度者 Dep,把消息推知给调度者;

  • 调度者也有两个方法,绑定订阅者 Sub,把消息推送给订阅者;

  • 订阅者有一个方法,执行函数;


这里面最重要的是有一个回调函数,作为调度中心的入参,会传给 Sub 执行;


于是,本篇带来 简化 了的思路进行理解:


比方说天气预报这个场景:气象站是需要发布信息的;建筑工地、船舶行业、普通游客是需要这些信息的;


如果我们直接强绑定这个通知关系,即:


function weatherWarning(weatherStatus){  if(weatherStatus==='warning'){ // 糟糕的天气      buildingsite.stopwork() // 工地停工      ships.mooring() // 船舶停航      tourists.canceltrip() // 旅游取消  }}
weatherWarning("warning") // 发布坏天气通知
复制代码



图片来源


这样做,有无毛病?


还得是它俩:有毛病!违背开闭原则、违背单一职责原则;


违背开闭原则:上例中,如果有新的群体需要获取天气信息,要不断修改 weatherWarning 函数;


违背单一职责原则:上例中,任何一个群体代码执行错误,都会影响 weatherWarning 函数体代码的向下执行;


所以,还得改,于是引入:调度中心 Dep,这里叫 EventEmit



图片来源


由调度中心来绑定需要信息的群体,即绑定订阅器,然后由调度中心发布信息给订阅者;


const EventEmit = function() { // 调度中心  this.events = {};  this.on = function(name, cb) { // 绑定订阅器    if (this.events[name]) {      this.events[name].push(cb); // 支持同一个订阅器执行多个事情    } else {      this.events[name] = [cb];    }  };  this.trigger = function(name, ...arg) { // 发送消息    if (this.events[name]) {      this.events[name].forEach(eventListener => {        eventListener(...arg);      });    }  };};
复制代码


let weatherEvent = new EventEmit() // 实例化一个调度中心
weatherEvent.on('warning', function () { // 绑定发布通知的关系 // buildingsite.stopwork() console.log('buildingsite.stopwork()')})
weatherEvent.on('warning', function () { // 绑定发布通知的关系 // ships.mooring() console.log('ships.mooring()')})
weatherEvent.on('warning', function () { // 绑定发布通知的关系 // tourists.canceltrip() console.log('tourists.canceltrip()')})
weatherEvent.trigger('warning') // 发布消息
复制代码


当项目中存在一对多的依赖,且每个模块相对独立,可以考虑使用发布订阅模式来重构代码,即由调度中心来绑定、通知。


有工友可能疑问:这个怎么和之前说的【观察者模式】长得那么像?


class Subject{// 被观察者    constructor(){        this.observers=[]    }    add(observer){        this.observers.push(observer)    }    notify(weatherStatus){        this.observers.forEach(i=>i(weatherStatus))    }}
let sub = new Subject()
sub.add((reason)=>{ // buildingsite.stopwork() console.log('工地停工,因为天气:',reason)}) sub.add((reason)=>{ // ships.stopwork() console.log('船舶停航,因为天气:',reason)})sub.add((reason)=>{ // tourists.canceltrip() console.log('旅游取消,因为天气:',reason)})
sub.notify("warning") // sub 发布消息
// 工地停工,因为天气: warning// 船舶停航,因为天气: warning// 旅游取消,因为天气: warning
复制代码


没错,我们可以再简化理解:观察者模式是发布订阅模式的一部分,如果你把被观察者视作调度中心的话呢,这就是发布订阅模式,如果你把订阅中心视作被观察者,那就是观察者模式;两者是可以互相转化的。


观察者模式:A 推给 ob1、ob2、ob3,一对多;


发布订阅模式: A 推给 Dep ,Dep 再推给 ob1、ob2、ob3,一对一,再对多;


发布订阅模式应该是我们前端开发者最常用的设计模式:


element.addEventListener('click', function(){   //... })
复制代码


<hr>


OK,以上便是本篇分享。点赞关注评论,为好文助力👍

我是掘金安东尼 🤠 100 万阅读量人气前端技术博主 💥 INFP 写作人格坚持 1000 日更文 ✍ 关注我,陪你一起度过漫长编程岁月 🌏

发布于: 3 小时前阅读数: 14
用户头像

还未添加个人签名 2022.07.14 加入

社会我瓜哥,人狠话不多😎 微信 anthony1453,加我交个朋友😎 正联合【机械工业出版社】出版《程序员成长手册》,敬请期待😎 真正的大师,永远怀着一颗学徒的心(易)😎

评论

发布
暂无评论
简化理解:发布订阅_前端_掘金安东尼_InfoQ写作社区