设计模式之发布—订阅模式和观察者模式的区别

定义区别

首先,观察者是经典软件设计模式中的一种,但发布订阅只是软件架构中的一种消息范式。所以不要再被"观察者模式和发布订阅模式xxx"这样的问题误导。

发布------订阅模式

发布订阅模式(Publish-Subscribe Pattern)是一种软件架构设计 模式。这属于行为型设计模式,用来解耦生产者(发布者)和消费者(订阅者)之间的关系。在这种模式中,发布者负责发布信息,订阅者则可以选择订阅他们感兴趣的消息类型。当有新信息发布时,订阅者就会收到通知并执行相应操作。

发布订阅的核心是基于一个中心来建立整个体系。其中发布者和订阅者是不会直接进行通信的,

重要概念

  1. 发布者(Publisher): 也被称为消息生产者或发送者,负责生成和发送消息。发布者也不知道这些消息会被谁接收。
  2. 订阅者(Subscriber): 也被称为信息消费者或接受者,等待并响应来自发布者的信息。订阅者只接受他们感兴趣的信息。
  3. 频道/主题(Topic): 这时信息的分类方式,发布者会将信息发送到特定的主题,订阅者则会订阅自己感兴趣的主题。
  4. 信息代理(Message Broker): 在一些实现中,为了更好管理发布者和订阅者之间的交互,通常会设置一个中间件------让消息代理来处理信息的路由。确保信息从发布者正确地流向感兴趣的订阅者。

这张图能更好的帮助你理解发布订阅模式。

优缺点

优点

  • 松耦合: 主题和观察者之间的联系很弱,主题只知道观察者实现了某个接口,而并不关心他们是如何实现的。这使得我们可以独立复用主题或观察者中的任何一个。
  • 扩展性好: 新的观察者可以在不修改主题的前提下添加到系统中,提高了系统的可扩展性。
  • 促进广播通信: 一次的状态变化可以传递给多个观察者,支持广播式的通信机制。

缺点

  • 导致内存泄漏: 如果观察者没有正确地从主题中注销自己,可能导致主题持有不再需要的观察者引用,从而阻止垃圾回收。
  • 难以追踪: 如果观察者和被观察者之间的间接交互,调节和理解程序的行为可能变得更加复杂。
  • 潜在性能问题: 如果有大量的观察者,通知过程可能变得低效,尤其是需要同步通知的情况。

实现代码

js 复制代码
class PubSub {
  constructor() {
    this.messages = {};
    this.listeners = {};
  }
  // 添加发布者
  publish(type, content) {
    const existContent = this.messages[type];
    if (!existContent) {
      this.messages[type] = [];
    }
    this.messages[type].push(content);
  }
  // 添加订阅者
  subscribe(type, cb) {
    const existListener = this.listeners[type];
    if (!existListener) {
      this.listeners[type] = [];
    }
    this.listeners[type].push(cb);
  }
  // 通知
  notify(type) {
    const messages = this.messages[type];
    const subscribers = this.listeners[type] || [];
    subscribers.forEach((cb, index) => cb(messages[index]));
  }
}

发布者代码:

js 复制代码
class Publisher {
  constructor(name, context) {
    this.name = name;
    this.context = context;
  }
  publish(type, content) {
    this.context.publish(type, content);
  }
}

订阅者代码:

js 复制代码
class Subscriber {
  constructor(name, context) {
    this.name = name;
    this.context = context;
  }
  subscribe(type, cb) {
    this.context.subscribe(type, cb);
  }
}

使用代码:

js 复制代码
const TYPE_A = 'music';
const TYPE_B = 'movie';
const TYPE_C = 'novel';

const pubsub = new PubSub();

const publisherA = new Publisher('publisherA', pubsub);
publisherA.publish(TYPE_A, 'we are young');
publisherA.publish(TYPE_B, 'the silicon valley');
const publisherB = new Publisher('publisherB', pubsub);
publisherB.publish(TYPE_A, 'stronger');
const publisherC = new Publisher('publisherC', pubsub);
publisherC.publish(TYPE_C, 'a brief history of time');

const subscriberA = new Subscriber('subscriberA', pubsub);
subscriberA.subscribe(TYPE_A, res => {
  console.log('subscriberA received', res)
});
const subscriberB = new Subscriber('subscriberB', pubsub);
subscriberB.subscribe(TYPE_C, res => {
  console.log('subscriberB received', res)
});
const subscriberC = new Subscriber('subscriberC', pubsub);
subscriberC.subscribe(TYPE_B, res => {
  console.log('subscriberC received', res)
});

pubsub.notify(TYPE_A);
pubsub.notify(TYPE_B);
pubsub.notify(TYPE_C);

观察者模式

观察者模式(Observer Pattern) 是一种行为设计模式,定义了对象之间的一对多的依赖关系,使得一个对象("主题"或"被观察者")状态的变化可以自动通知其依赖的对象("观察者")。

这个模式至少有一个"被观察对象",可以有多个观察者来观察这个对象。他们之间的关系是由被观察者 主动建立的,而被观察者至少要有三种方法------添加观察者、移除观察者、通知观察者。

当被观察者将某个观察者添加到自己的观察者列表 后,观察者和被观察者的联系就建立起来了。之后只要被观察者在某一时刻触发通知观察者这个方法,观察者就可以收到来自观察者的信息。

重要概念

  1. 主题/被观察者(Subject):这是一个接口或抽象类,定义了添加、删除和通知观察者的功能。具体主题实现了这些方法,并在状态变化时通知所有已注册的观察者。
  2. 观察者(Observer): 这是另一个接口或抽象类,定义了一个更新的方法,这个方法是由主题来通知观察者发生了状态变化。具体观察者实现了这个接口以及如何响应主题的通知。
  3. 具体主题(ConcreteSubject):实现了Subject接口,包含一些能触发状态变化的方法,并在状态变化时通知观察者。
  4. 具体观察者(ConcreteObserver):实现了Observer接口,保持对主题状态的引用,并根据主题的状态更新自己。

优缺点

优点

  • 松耦合: 主体和观察者之间相互独立,它们可以在互不影响的情况下开发、测试、复用和扩展。
  • 可扩展性: 新类型的观察者会很容易添加进来,不需要修改主题代码。
  • 促进广播通信: 一次状态变化可以传递给多个观察者,简化了信息的传播。

缺点

  • 潜在性能问题: 如果有很多观察者,通知过程会变得很低效,特别是同步通知的情况下。
  • 内存泄漏: 如果没有正确地从主题中注销观察者,可能导致内存泄漏,因为主题可能存在不再需要的观察者的引用。
  • 复杂度增加: 随着观察者数量的增加,理解系统的行为变得困难。

实现代码

被观察者:

js 复制代码
class Subject {

  constructor() {
    this.observerList = [];
  }

  addObserver(observer) {
    this.observerList.push(observer);
  }

  removeObserver(observer) {
    const index = this.observerList.findIndex(o => o.name === observer.name);
    this.observerList.splice(index, 1);
  }

  notifyObservers(message) {
    const observers = this.observeList;
    observers.forEach(observer => observer.notified(message));
  }

}

观察者:

js 复制代码
class Observer {

  constructor(name, subject) {
    this.name = name;
    if (subject) {
      subject.addObserver(this);
    }
  }

  notified(message) {
    console.log(this.name, 'got message', message);
  }
}

使用代码:

js 复制代码
const subject = new Subject();
const observerA = new Observer('observerA', subject);
const observerB = new Observer('observerB');
subject.addObserver(observerB);
subject.notifyObservers('Hello from subject');
subject.removeObserver(observerA);
subject.notifyObservers('Hello again');

二者的区别

1. 信息传递机制:

  • 观察者模式: 通常是依赖直接引用或注册机制来连接主题和观察者。当主题发生变化时,会直接调用所有已注册观察者的更新方法。
  • 发布订阅模式: 引入一个中间件(信息代理)来管理主题和订阅者之间的交互。发布者发布信息到某个主题,然后由信息代理将这些信息转发给所有订阅了该主题的订阅者。这使得发布者和订阅者完全解耦,二者不需要知道对方的存在。 2. 耦合程度:
  • 观察者模式: 虽然实现了耦合,但还是需要观察者向主题注册,而且主题需要维护一个观察者列表并通知他们。
  • 发布订阅模式: 利用消息代理进一步减少了组件间的耦合度,发布者只需要发布,订阅者只关心接收的信息。 3. 灵活性和可扩展性:
  • 观察者模式: 因为直接的依赖关系,所以在处理大规模系统或动态添加移除观察者比不上发布订阅模式。
  • 发布订阅模式: 因为由信息代理来间接处理信息,所以支持更加复杂的信息路由策略、过滤和异步通信等。 4. 复杂度:
  • 观察者模式: 相对简单,适用于简单场景。
  • 发布订阅模式: 相对复杂,需要考虑额外的信息基础设施,而且还特别要注意错误处理、可靠性和性能优化等。

我们再来看个例子来区分这两种模式。

观察者模式:

想象一下你订阅了一个杂志。在这个场景中,杂志出版商(相当于"被观察者"或"主题")直接管理着一个订阅者的列表,也就是那些已经订阅了该杂志的读者(相当于"观察者")。每当有新一期杂志出版时,出版商会直接将新杂志寄送给所有订阅者。

发布订阅模式:

现在考虑一种不同的情况:社交媒体平台上的信息流更新。假设你在微博上关注了一些博主。当你关注某个博主时,实际上是告诉微博平台(消息代理)你想接收来自这些博主的新动态。当博主发布新内容时,他们只是简单地将这条新信息发布到微博平台上,而不需要知道谁会看到这条消息。微博平台则根据用户的关注列表,自动将这条新动态推送给所有关注该博主的用户。

通过这两个例子想必能帮你更好理解这两种模式。

相关推荐
李小白666 分钟前
Vue背景介绍+声明式渲染+数据响应式
前端·javascript·vue.js
木子庆五10 分钟前
Android设计模式之工厂方法模式
android·设计模式·工厂方法模式
萌萌哒草头将军19 分钟前
🚀🚀🚀Zod 深度解析:TypeScript 运行时类型安全的终极实践指南
javascript·vue.js·react.js
烛阴38 分钟前
JavaScript yield与异步编程
前端·javascript
Moment1 小时前
从 Webpack 源码来深入学习 Tree Shaking 实现原理 🤗🤗🤗
前端·javascript·webpack
冴羽1 小时前
SvelteKit 最新中文文档教程(15)—— 链接选项
前端·javascript·svelte
李是啥也不会2 小时前
如何通过JavaScript实现点击播放音频
开发语言·javascript·音视频
boy快快长大2 小时前
【VUE】day08黑马头条小项目
前端·javascript·vue.js
猫猫头有亿点炸2 小时前
vue.js前端条件渲染指令相关知识点
java·前端·javascript
Java&Develop2 小时前
vue2拦截器 拦截后端返回的数据,并判断是否需要登录
前端·javascript·vue.js