2种消息通知模式:观察者VS发布订阅

139 阅读4分钟

你有没有遇到过这样的场景:当一个数据变化时,多个地方都需要跟着更新?比如用户修改了头像,个人资料页、评论区、聊天窗口都要同步显示新头像。这时候,设计模式中的「观察者模式」和「发布订阅模式」就派上用场了。今天带你彻底搞懂这对「孪生兄弟」!

一、观察者模式:老师点名,学生答到

观察者模式的核心思想就是「你盯着我,我一动你就跟着动」。它定义了对象间的一对多依赖关系,当被观察者状态变化时,会自动通知所有观察者。

生活例子:老师点名

最形象的类比就是老师点名。老师(被观察者)念到学生名字时,学生(观察者)必须立刻答到。老师直接认识每一个学生,学生也知道自己要对老师的点名做出反应。

代码实战:DOM属性监听

我们用JavaScript实现一个简单的观察者模式:

// HTML结构
// <h2>1</h2>
// <button>点击</button>

let btn = document.querySelector('button');
let h2 = document.querySelector('h2');
let obj = { count: 1 };
let num = obj.count; // 防止递归爆栈

function observer(value) {
    // 找到订阅者(h2),更新内容
    h2.innerHTML = value;
}

Object.defineProperty(obj, 'count', {
    get() { return num; },
    set(value) {
        observer(value); // 通知观察者
        num = value;
    }
});

btn.addEventListener('click', () => { // 每点击一下, h2标签的值+1
    obj.count++;
    console.log('当前count:', obj.count);
});

在这个例子中,我们通过Object.defineProperty监听对象属性的变化。当obj.count被修改时,会自动调用observer函数更新DOM。这就是观察者模式的核心:被观察者状态变化时,直接通知观察者。

优点

  • 实现简单,容易理解
  • 响应迅速,被观察者变化立即通知观察者
  • 适合小范围、强关联的场景

二、发布订阅模式:小区公告栏,物业贴通知

发布订阅模式则是「你别盯着我,咱们都找小黑板(事件中心)留言」。它通过一个事件中心来解耦发布者和订阅者,三者互不认识。

生活例子:小区公告栏

物业(发布者)把通知贴在公告栏(事件中心),业主(订阅者)自己去公告栏看通知。物业不需要知道哪些业主会看通知,业主也不需要知道通知是谁发的。大家只和公告栏打交道。

代码实战:EventEmitter实现

我们来实现一个简单的事件中心:

class EventEmitter {
    constructor() {
        this.eventList = {};
    }
    on(eventName, callback) {
        if (!this.eventList[eventName]) {
            this.eventList[eventName] = [];
        }
        this.eventList[eventName].push(callback);
    }
    off(eventName, callback) {
        const callbacks = this.eventList[eventName];
        const index = callbacks.indexOf(callback);
        if (index !== -1) {
            callbacks.splice(index, 1);
        }
    }
    once(eventName, callback) {
        const wrap = () => {
            callback();
            this.off(eventName, wrap);
        };
        this.on(eventName, wrap);
    }
    emit(eventName) {
        if (this.eventList[eventName]) {
            const handlers = this.eventList[eventName].slice();
            handlers.forEach(item => item());
        }
    }
}

// 使用示例
function hu() { console.log('胡买房'); }
function lai() { console.log('赖买房'); }
function zhong() { console.log('钟买车位'); }

let _event = new EventEmitter();
_event.on('hasHouse', hu);
_event.once('hasHouse', lai);
_event.on('hasCarport', zhong);

_event.emit('hasHouse');  // 胡和赖都买房
_event.emit('hasHouse');  // 赖只买一次, 只有胡继续买房
_event.emit('hasCarport');  // 钟买车

在这个例子中,EventEmitter就是事件中心。发布者调用emit方法发布事件,订阅者通过on方法订阅事件。发布者和订阅者之间没有直接联系,完全通过事件中心进行通信。

优点

  • 解耦性强,发布者和订阅者互不依赖
  • 灵活性高,可以轻松添加或移除订阅者
  • 适合复杂、跨模块的通信场景

三、核心对比:一张表看明白

特性观察者模式发布订阅模式
耦合度紧耦合松耦合
通信方式直接通知事件中心转发
角色数量两个(被观察者、观察者)三个(发布者、订阅者、事件中心)
适用场景UI响应、数据绑定跨模块、全局事件管理
代码复杂度简单略高

四、应用场景举例

观察者模式的应用

  • Vue响应式系统:当数据变化时,视图自动更新
  • Excel单元格联动:一个单元格的值变化,关联的单元格自动更新
  • 游戏中的成就系统:当玩家达到条件时,立即解锁成就

发布订阅模式的应用

  • Node.js EventEmitter:处理异步事件
  • 浏览器自定义事件:不同组件间通信
  • 消息队列:如RabbitMQ、Kafka等,实现系统间解耦通信
  • 微信公众号:作者发布文章,订阅的用户收到通知

五、总结

观察者模式就像「班主任和学生」,老师一喊,学生立刻响应;发布订阅模式就像「微信群公告」,群主发消息,谁看谁响应,群主不用知道谁看了。

选择哪种模式,取决于你的具体需求:如果是小范围、强关联的场景,观察者模式简单直接;如果是复杂、跨模块的通信,发布订阅模式更灵活。