行为型:发布订阅模式

行为,发布,订阅,模式 · 浏览次数 : 235

小编点评

**发布者类 Pulisher** ```javascript constructor (name, evtCenter) { this.name = name; this.evtCenter = evtCenter; } register (evtName, callback) { if (!this.evtHandles[evtName]) { this.evtHandles[evtName] = [callback]; } this.evtHandles[evtName].push(callback); } publish (evtName, ...arg) { if (this.evtHandles[evtName]) { for (let fn of this.evtHandles[evtName]) { fn.call(this, ...arg); } } } unregister (evtName, fn) { let fnList = this.evtHandles[evtName]; if (!fnList) return false; if (!fn) { // 不传入指定取消的订阅方法,则清空所有key下的订阅 this.evtHandles[evtName] = []; } else { fnList.forEach((item, index) => { if (item === fn) { fnList.splice(index, 1); } }); } } ``` **事件调度中心 PubSub** ```javascript constructor () { this.evtHandles = {}; } subscribe (evtName, callback) { if (!this.evtHandles[evtName]) { this.evtHandles[evtName] = [callback]; } this.evtHandles[evtName].push(callback); } publish (evtName, ...arg) { if (this.evtHandles[evtName]) { for (let fn of this.evtHandles[evtName]) { fn.call(this, ...arg); } } } unSubscribe (evtName, fn) { let fnList = this.evtHandles[evtName]; if (!fnList) return false; if (!fn) { // 不传入指定取消的订阅方法,则清空所有key下的订阅 this.evtHandles[evtName] = []; } else { fnList.forEach((item, index) => { if (item === fn) { fnList.splice(index, 1); } }); } } ``` **测试** ```javascript const pub1 = new PubSub() // 订阅三个事件 pub1.subscribe('onWork', time => { console.log(`上班了:${time}`); }); pub1.subscribe('onWork', time => { console.log(`上班了:${time},开始打开待办事项`); }); pub1.subscribe('onOffWork', time => { console.log(`下班了:${time}`); }); // 发布对应的事件 pub1.publish('onWork', '09:00:00'); pub1.publish('onLaunch', '12:00:00'); pub1.publish('onOffWork', '18:00:00'); ```

正文

定义  

  发布订阅模式是基于一个事件(主题)通道,希望接收通知的对象Subscriber (订阅者)通过自定义事件订阅主题,被激活事件的对象 Publisher (发布者)通过发布主题事件的方式通知订阅者 Subscriber (订阅者)对象。

  简单说就是发布者与订阅者通过事件来通信,这里的发布者是之前观察者模式中的被观察者,订阅者是观察者模式中的观察者,他们角色定位是等价的,只不过是不同的叫法。

发布订阅与观察者模式

  平时我们在微博中关注某个大v,这个大v 并不关心我这个订阅者具备什么特征,我只是通过微博这个平台关注了他,他也只是把他要分享的话题通过微博发出来,我和他之间并不存在直接的联系,然后我自动就能看到这个大v发布的消息,这就是发布订阅模式。

  发布订阅者模式与观察者模式类似,但是两者并不完全相同,发布订阅者模式与观察者相比多了一个中间层事件调度中心,用来对发布者发布的信息进行处理,再通知到各个特定的订阅者,大致过程如下图所示

发布者只是发布某事件,无需知道哪些订阅者,订阅者只需要订阅自己感兴趣的事件,无需关注发布者。

发布者完全不用感知订阅者,不用关心它怎么实现回调方法,事件的注册和触发都发生在独立于双方的第三方平台(调度中心)上,发布-订阅模式下,实现了完全地解耦。

通过之前对观察者模式的实现,我们的Subject类中是持有observer对象的,因此并没有实现两个类的完全解耦。通过添加中间层的调度中心类,我么可以将订阅者和发布者完全解耦,两者不再有直接的关联,而是通过调度中心关联起来。下面我们实现一个发布订阅者模式。

传统写法模拟发布订阅模式

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

  1. 发布者 ---- 观察者模式中的【被观察者】
  2. 订阅者 ---- 观察者模式中的【订阅者】
  3. 事件中心 ---- 类似公共的一个平台
/*
发布者:发布、注册xxx事件 or 主题
订阅者:自己的行为,取消订阅,订阅
事件中心:注册发布者的某事件、取消注册发布者的某事件、注册订阅者、取消订阅者、发布事件(通知订阅者)
*/
// 发布者
class Pulisher {
  constructor (name, evtCenter) {
    this.name = name;
    this.evtCenter = evtCenter;
  }
  // 向事件调度中心-注册某事件
  register (evtName) {
    this.evtCenter.registerEvt(evtName)
  }
  unregister (evtName) {
    this.evtCenter.unRegisterEvt(evtName)
  }
  // 向事件调度中心-发布某事件
  publish (evtName, ...params) {
    this.evtCenter.publish(evtName, ...params)
  }
}

// 订阅者
class Subscriber {
  constructor (name,evtCenter) {
    this.name = name;
    this.evtCenter = evtCenter;
  }
  //订阅
  subscribe(evtName) {
    this.evtCenter.addSubscribe(evtName, this);
  }
  //取消订阅
  unSubscribe(evtName) {
    this.evtCenter.unAddSubscribe(evtName, this);
  }
  //接收
  update(params) {
    console.log(`我接收到了,${params}`);
  }
}

// 事件调度中心
class EvtCenter {
  constructor (name) {
    this.name = name;
    this.evtHandle = {}
  }
  // 注册发布者要发布的事件
  registerEvt (evtName) {
    if (!this.evtHandle[evtName]) {
      this.evtHandle[evtName] = []
    }
  }
  // 取消注册发布者要发布的事件
  unRegisterEvt (evtName) {
    delete this.evtHandle[evtName];
  }
  // 增加订阅者-注册观察者
  addSubscribe(evtName, sub) {
    if (this.evtHandle[evtName]) {
      this.evtHandle[evtName].push(sub);
    }
  }
   // 取消订阅者-移除注册观察者
   unAddSubscribe(evtName, sub) {
    this.evtHandle[evtName].forEach((item, index) => {
      if (item === sub) {
        this.evtHandle[evtName].splice(index, 1);
      }
    });
  }

  // 事件调度中心-发布某事件
  publish (evtName, ...params) {
    this.evtHandle[evtName] && this.evtHandle[evtName].forEach((item) => {
      item.update(...params);
    });
  }
}

// 测试
const evtCenter1 = new EvtCenter('报社调度中心1')

const pulisher1 = new Pulisher('报社1', evtCenter1)

const sub1 = new Subscriber('我是sub1, 我对日报感兴趣', evtCenter1)
const sub2 = new Subscriber('我是sub2, 我对日报感兴趣', evtCenter1)
const sub3 = new Subscriber('我是sub3, 我对中报感兴趣', evtCenter1)
const sub4 = new Subscriber('我是sub4, 我对晚报感兴趣', evtCenter1)

// 发布者-注册三个事件到事件中心
pulisher1.register('广州日报')
pulisher1.register('广州中报')
pulisher1.register('广州晚报')

// 订阅者可以自己订阅,当然也可以直接操作事件中心
sub1.subscribe('广州日报')
sub2.subscribe('广州日报')
sub3.subscribe('广州中报')
sub4.subscribe('广州晚报')

// 现在开始发布事件
pulisher1.publish('广州日报', '广州日报')
pulisher1.publish('广州中报', '广州中报')
pulisher1.publish('广州晚报', '广州晚报')

pulisher1.unregister('广州日报')

// 再一次发布事件
console.log('再一次发布事件,这次我取消了日报') // 没有输出广州日报
pulisher1.publish('广州日报', '广州日报')
pulisher1.publish('广州中报', '广州中报')
pulisher1.publish('广州晚报', '广州晚报')

简单写法--面向事件调度中心编程

在js中函数是第一等公民,天生适合回调函数,所以可以直接面向事件调度中心编码即可。我们要做的事情其实就是触发什么事件,执行什么动作。

// 事件调度中心
class PubSub  {
  constructor () {
    this.evtHandles = {}
  }
  // 订阅
  subscribe (evtName, callback) {
    if (!this.evtHandles[evtName]) {
      this.evtHandles[evtName] = [callback];
    }
    this.evtHandles[evtName].push(callback);
  }
  // 发布
  publish(evtName, ...arg) {
    if (this.evtHandles[evtName]) {
      for(let fn of this.evtHandles[evtName]) {
        fn.call(this, ...arg);
      }
    }
  }
  unSubscribe (evtName, fn) {     // 取消订阅
    let fnList = this.evtHandles[evtName];
    if (!fnList) return false;

    if (!fn) {
      // 不传入指定取消的订阅方法,则清空所有key下的订阅
      this.evtHandles[evtName] = []
    } else {
      fnList.forEach((item, index) => {
        if (item === fn) {
          fnList.splice(index, 1);
        }
      })
    }
  }
}
// 先订阅在发布
const pub1 = new PubSub()

// 订阅三个事件
pub1.subscribe('onWork', time => {
  console.log(`上班了:${time}`);
})
pub1.subscribe('onWork', time => {
  console.log(`上班了:${time},开始打开待办事项`);
})

pub1.subscribe('onOffWork', time => {
  console.log(`下班了:${time}`);
})

pub1.subscribe('onLaunch', time => {
  console.log(`吃饭了:${time}`);
})

// 发布对应的事件
pub1.publish('onWork', '09:00:00');
pub1.publish('onLaunch', '12:00:00');
pub1.publish('onOffWork', '18:00:00');
// 取消onWork 事件
pub1.unSubscribe('onWork');


// 取消订阅
pub1.unSubscribe('onWork');
console.log(`取消 onWork`);
pub1.publish('onWork', '09:00:00'); // 不会执行

小结

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

与行为型:发布订阅模式相似的内容:

行为型:发布订阅模式

定义 发布订阅模式是基于一个事件(主题)通道,希望接收通知的对象Subscriber (订阅者)通过自定义事件订阅主题,被激活事件的对象 Publisher (发布者)通过发布主题事件的方式通知订阅者 Subscriber (订阅者)对象。 简单说就是发布者与订阅者通过事件来通信,这里的发布者是之前

软件设计模式系列之二十一——观察者模式

观察者模式(Observer Pattern)是一种行为型设计模式,它允许对象之间建立一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。这个模式也被称为发布-订阅模式,因为它模拟了一个主题(发布者)与多个观察者(订阅者)之间的关系。观察者模式主要用于实现对象之间...

CAP 8.2 版本发布通告

前言 今天我们很高兴宣布 CAP 发布 8.2 版本正式版,我们在这个版本中主要致力于对订阅着并行执行的特性提供支持,同时添加了对在订阅者中对消息头的控制行为。 下面,具体看一下我们新版本的功能吧。 总览 可能有些人还不知道 CAP 是什么,老规矩来一个简介。 CAP 是一个用来解决微服务或者分布式

Squirrel状态机-从原理探究到最佳实践

作者:京东物流 郑朋辉 1 简介 Squirrel状态机是一种用来进行对象行为建模的工具,主要描述对象在它的生命周期内所经历的状态,以及如何响应来自外界的各种事件。比如订单的创建、已支付、发货、收获、取消等等状态、状态之间的控制、触发事件的监听,可以用该框架进行清晰的管理实现。使用状态机来管理对象生

行为型:观察者模式

定义 观察者模式属于行为型模式,它定义了对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,并自动更新。 一种一对多的关系中一称为被观察者也叫目标对象Subject而多则称为观察者对象Observer 观察者模式中通常有两个模型,一个观察者(observer)和

软件设计模式系列之二十二——状态模式

状态模式是一种行为型设计模式,它允许对象在内部状态发生改变时改变其行为,使得对象的行为看起来像是改变了其类。状态模式将对象的状态抽象成一个独立的类,让对象在不同状态下具有不同的行为,而且可以在运行时切换状态。这种方式使得状态的管理更加清晰,避免了大量的条件判断语句,提高了代码的可维护性和可扩展性。

LangGraph实战

1.概述 前段时间LangChain发布了LangGraph,它引起了很多关注。LangGraph 的主要优势在于它能够实现循环工作流,这对于在 LLM 应用程序中模拟类似代理的行为至关重要。本篇博客,笔者将从介绍 LangGraph 的功能和用例,强调它与典型的有向无环图 (DAG)工作流的区别,

【设计模式】观察者模式

设计模式 【设计模式】工厂方法模式 【设计模式】抽象工厂模式 【设计模式】单例模式 【设计模式】策略模式 【设计模式】观察者模式 一、介绍 观察者模式是一种行为设计模式,当一个对象的状态发生改变时,依赖(观察)它的对象会接收到通知,并进行自动的更新操作。 举例:某公司发布了一款新的手机,性能很强大,

妙用OSGraph:发掘GitHub知识图谱上的开源故事

OSGraph (Open Source Graph) 是一个开源图谱关系洞察工具,基于GitHub开源数据全域图谱,实现开发者行为、项目社区生态的分析洞察。可以为开发者、项目Owner、开源布道师、社区运营等提供简洁直观的开源数据视图,帮助你和你的项目制作专属的开源名片、寻求契合的开发伙伴、挖掘深...

4.7 x64dbg 应用层的钩子扫描

所谓的应用层钩子(Application-level hooks)是一种编程技术,它允许应用程序通过在特定事件发生时执行特定代码来自定义或扩展其行为。这些事件可以是用户交互,系统事件,或者其他应用程序内部的事件。应用层钩子是在应用程序中添加自定义代码的一种灵活的方式。它们可以用于许多不同的用途,如安全审计、性能监视、访问控制和行为修改等。应用层钩子通常在应用程序的运行时被调用,可以执行一些预定义的