Skip to content

发布订阅模式

发布/订阅模式(Publish/Subscribe Pattern,简称 Pub/Sub 模式)是一种设计模式,它通过一个事件通道来解耦消息的发送者(发布者)和接收者(订阅者)。在发布/订阅模式中,发布者不会直接向特定的接收者发送消息,而是将消息发布到一个主题或频道上;同样地,订阅者也不直接从某个发布者那里接收消息,而是订阅一个或多个主题,并对这些主题上的消息做出响应。这种模式非常适合用于构建松散耦合的应用程序。

在 JavaScript 中的实现

下面是一个简单的发布/订阅模式的实现示例:

javascript
class PubSub {
  constructor() {
    this.subscribers = {};
  }

  // 订阅方法
  subscribe(eventType, fn) {
    if (!this.subscribers[eventType]) {
      this.subscribers[eventType] = new Set();
    }
    this.subscribers[eventType].add(fn);
  }

  // 发布方法
  publish(eventType, data) {
    if (!this.subscribers[eventType]) return;

    this.subscribers[eventType].forEach((callback) => {
      callback(data);
    });
  }

  // 取消订阅方法
  unsubscribe(eventType, fn) {
    if (this.subscribers[eventType]) {
      this.subscribers[eventType].delete(fn);
    }
  }
}

// 使用示例
const pubsub = new PubSub();

function subscriberOne(data) {
  console.log("Subscriber One Received:", data);
}

function subscriberTwo(data) {
  console.log("Subscriber Two Received:", data);
}

pubsub.subscribe("news", subscriberOne);
pubsub.subscribe("news", subscriberTwo);

pubsub.publish("news", "Breaking News!");

pubsub.unsubscribe("news", subscriberOne);

pubsub.publish("news", "Another Update");

代码解释

  • 构造函数:初始化一个空对象 subscribers 来存储所有订阅者的回调函数。
  • subscribe 方法:允许订阅者注册对特定类型事件的兴趣。如果该类型的订阅者集合不存在,则创建一个新的集合,并将提供的回调函数添加到这个集合中。
  • publish 方法:当有事件发生时,发布者调用此方法并将数据传递给所有已订阅该事件类型的回调函数。
  • unsubscribe 方法:允许订阅者取消对特定类型事件的关注。

应用场景

发布/订阅模式在前端开发中有多种应用场景,例如:

  • 事件总线(Event Bus):在大型应用中,组件之间的通信可以通过事件总线来实现,而不需要直接引用彼此。
  • 异步任务处理:当需要处理来自不同来源的数据更新或用户操作时,可以使用发布/订阅模式来通知相关的部分进行相应的更新。
  • 模块间通信:在复杂的系统中,不同的模块可能需要互相通讯但又不想紧密耦合,这时就可以利用发布/订阅模式来实现。

这种方式使得系统更加灵活、可扩展,同时也简化了组件间的依赖关系管理。

与观察者模式的区别

观察者模式(Observer Pattern)和发布-订阅模式(Publish/Subscribe Pattern,简称 Pub/Sub)都是设计模式中用于处理对象间一对多依赖关系的行为模式。它们允许某些对象监听其他对象的变化,并在目标对象状态改变时得到通知。尽管两者有相似的目的,但在实现细节和概念上有一些差异。

观察者模式

观察者模式是一种对象行为模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象发生变化时,它的所有依赖者(观察者)都会收到通知并自动更新。

主要角色:

  1. Subject(主题):知道其观察者,提供注册和删除观察者的接口。
  2. Observer(观察者):为那些在主题发生改变时需获得通知的对象定义一个更新接口。
  3. ConcreteSubject(具体主题):存储相关状态,当状态变化时通知其观察者。
  4. ConcreteObserver(具体观察者):维护一个指向具体主题对象的引用,存储有关主题的状态以保持一致。

特点:

  • 观察者直接订阅目标对象的变化。
  • 目标对象持有对观察者的引用,并在状态改变时主动通知观察者。

发布-订阅模式

发布-订阅模式是通过一个事件通道来实现的,这个通道解耦了消息发送者(发布者)与接收者(订阅者)。发布者不会发送信息给特定的接收者,而是发布感兴趣的消息;同样,订阅者只接收他们感兴趣的消息。

主要角色:

  1. Event Channel(事件通道):作为中介者,负责管理订阅者列表以及向正确的订阅者广播事件。
  2. Publisher(发布者):不需要知道订阅者的任何信息,仅需将消息发布到事件通道。
  3. Subscriber(订阅者):向事件通道订阅感兴趣的消息类型,当该类型的消息被发布时会收到通知。

特点:

  • 引入了一个第三方(事件通道或消息总线)来管理订阅者和发布者之间的通信。
  • 发布者和订阅者之间完全解耦,彼此不知道对方的存在。

比较

  • 耦合度:观察者模式中,主题和观察者之间存在直接的联系,即主题需要知道观察者。而在发布-订阅模式中,这种联系通过中间件(事件通道)进行解耦。
  • 灵活性:发布-订阅模式通常被认为更加灵活,因为它允许更复杂的过滤逻辑,比如基于消息内容的主题过滤。
  • 复杂性:发布-订阅模式由于引入了额外的组件(事件通道),因此可能比观察者模式稍微复杂一些。

在 JavaScript 中的实现示例

观察者模式的例子:

javascript
class Subject {
  constructor() {
    this.observers = [];
  }

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

  removeObserver(observer) {
    this.observers = this.observers.filter((obs) => obs !== observer);
  }

  notifyObservers(message) {
    this.observers.forEach((observer) => observer.update(message));
  }
}

class Observer {
  update(message) {
    console.log(`Received message: ${message}`);
  }
}

// 使用示例
const subject = new Subject();
const observer1 = new Observer();
const observer2 = new Observer();

subject.addObserver(observer1);
subject.addObserver(observer2);

subject.notifyObservers("Hello Observers!"); // 输出两次 "Received message: Hello Observers!"

发布-订阅模式的例子:

javascript
const eventChannel = {
  subscribers: {},

  subscribe(eventType, fn) {
    if (!this.subscribers[eventType]) {
      this.subscribers[eventType] = [];
    }
    this.subscribers[eventType].push(fn);
  },

  publish(eventType, data) {
    if (this.subscribers[eventType]) {
      this.subscribers[eventType].forEach((fn) => fn(data));
    }
  },

  unsubscribe(eventType, fn) {
    if (this.subscribers[eventType]) {
      this.subscribers[eventType] = this.subscribers[eventType].filter(
        (subscriber) => subscriber !== fn
      );
    }
  },
};

// 使用示例
function subscriberOne(data) {
  console.log(`Subscriber one received: ${data}`);
}

function subscriberTwo(data) {
  console.log(`Subscriber two received: ${data}`);
}

eventChannel.subscribe("event", subscriberOne);
eventChannel.subscribe("event", subscriberTwo);

eventChannel.publish("event", "Hello Subscribers!"); // 输出两条消息

这两种模式都有其独特的应用场景,选择哪一种取决于具体的业务需求和系统架构的设计考量。

Released under the MIT License.