深入理解JS中的发布订阅模式和观察者模式

18 阅读4分钟

发布/订阅模式(Publish/Subscribe)和观察者模式(Observer Pattern)在概念上非常相似,都是用于实现对象之间的松耦合通信。尽管它们在实现细节和使用场景上有所不同,但核心思想是相通的。

观察者模式

  • 直接通信:在观察者模式中,观察者(Observer)直接订阅主题(Subject)。当主题状态改变时,会直接通知所有订阅的观察者。
  • 紧密耦合:观察者需要直接注册到主题上,这意味着观察者和主题之间存在较紧密的耦合。
  • 实现方式:通常由主题维护一个观察者列表,当主题状态改变时,遍历这个列表,逐个调用观察者的更新方法。

简单观察者模式示例:

class Subject {
    constructor() {
        this.observers = []; // 观察者列表
    }

    // 添加观察者
    addObserver(observer) {
        this.observers.push(observer);
    }

    // 移除观察者
    removeObserver(observer) {
        const index = this.observers.indexOf(observer);
        if (index > -1) {
            this.observers.splice(index, 1);
        }
    }

    // 通知所有观察者
    notify(data) {
        this.observers.forEach(observer => observer.update(data));
    }
}

class Observer {
    constructor(name) {
        this.name = name;
    }

    // 接收到主题的通知时调用
    update(data) {
        console.log(`${this.name} received data: ${data}`);
    }
}

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

const observer1 = new Observer('Observer 1');
const observer2 = new Observer('Observer 2');

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

subject.notify('Hello World!'); // 通知所有观察者

subject.removeObserver(observer2);

subject.notify('Second message'); // 只有 observer1 会接收到这个通知

在这个示例中,Subject 类维护了一个观察者列表,提供了添加观察者、移除观察者和通知观察者的方法。Observer 类定义了观察者应有的update 方法,用于接收来自主题的通知。通过创建 Subject 和 Observer 的实例并调用相应的方法,我们演示了观察者模式的工作流程。

发布/订阅模式

  • 间接通信:发布/订阅模式通过一个称为“消息代理”或“事件总线”的第三方组件来管理事件和订阅者之间的通信,发布者发布事件到事件总线,而订阅者从事件总线订阅事件,发布者和订阅者不需要知道对方的存在。
  • 松耦合:由于发布者和订阅者不直接交互,因此它们之间的耦合度更低,这使得系统的各个部分可以更独立地发展和变化。
  • 实现方式:通常依赖于一个中间件或服务来传递消息,这个中间件负责存储订阅者信息和转发发布者的消息。
class EventBus {
    constructor() {
        this.events = {}; // 存储事件及其监听函数
    }

    // 订阅事件
    subscribe(eventName, callback) {
        if (!this.events[eventName]) {
            this.events[eventName] = []; // 如果事件不存在,则初始化为空数组
        }
        this.events[eventName].push(callback);
    }

    // 发布事件
    publish(eventName, data) {
        const callbacks = this.events[eventName];
        if (callbacks) {
            callbacks.forEach(callback => callback(data)); // 触发所有订阅了该事件的回调函数
        }
    }

    // 取消订阅
    unsubscribe(eventName, callback) {
        const callbacks = this.events[eventName];
        if (callbacks) {
            const index = callbacks.indexOf(callback);
            if (index > -1) {
                callbacks.splice(index, 1); // 移除指定的回调函数
            }
        }
    }
}

// 使用示例
const eventBus = new EventBus();

// 订阅事件
eventBus.subscribe('sayHello', name => console.log(`Hello ${name}!`));
eventBus.subscribe('sayGoodbye', name => console.log(`Goodbye ${name}!`));

// 发布事件
eventBus.publish('sayHello', 'Alice');
eventBus.publish('sayGoodbye', 'Bob');

// 取消订阅
const goodbyeCallback = name => console.log(`Goodbye ${name}!`);
eventBus.unsubscribe('sayGoodbye', goodbyeCallback);

// 再次发布事件,验证取消订阅是否成功
eventBus.publish('sayGoodbye', 'Charlie');

在这个示例中,EventBus 类提供了subscribe、publish 和 unsubscribe方法,分别用于订阅事件、发布事件和取消订阅事件。通过创建EventBus 的实例并调用相应的方法,我们演示了发布/订阅模式的工作流程。

使用场景

  • 观察者模式:通常用于实现对象之间的一对多依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。适用于对象之间的关系比较明确,且数量较少的情况。
  • 发布/订阅模式:适用于大型应用和系统,特别是在分布式系统中,用于解耦应用组件。由于其低耦合特性,它允许系统的不同部分独立地进行扩展和维护。常见于事件驱动的架构和消息队列系统中。

实际应用示例

  • 观察者模式:GUI 应用中的事件监听(如按钮点击)、模型-视图-控制器(MVC)模式中模型(Model)和视图(View)之间的关系。
  • 发布/订阅模式:消息队列系统(如 RabbitMQ、Kafka)、服务间的异步通信、Web 应用中的事件总线系统。