简化理解:发布订阅

举报
掘金安东尼 发表于 2022/08/11 10:00:57 2022/08/11
【摘要】 我们前不久写过发布订阅模式:// 发布者 Publisherclass Pub { constructor() { this.deps = []; } addDep(dep) { this.deps.push(dep); } publish(dep) { this.deps.forEach(item => item ==...

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

// 发布者 Publisher
class Pub {
    constructor() {
        this.deps = [];
    }
    addDep(dep) {
        this.deps.push(dep);
    }
    publish(dep) {
        this.deps.forEach(item => item === dep && item.notify());
    }
}
// 订阅者 Subscriber
class 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") // 实例化订阅者1
let sub2 = new Sub("订阅者2") // 实例化订阅者2

pub.addDep(dep) // 发布者绑定调度中心

dep.addSub(sub1) // 调度中心添加订阅者1
dep.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") // 发布坏天气通知

image.png
图片来源

这样做,有无毛病?

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

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

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

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

image.png
图片来源

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

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(){ 
  //... 
})

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

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

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。