观察者模式 vs 发布订阅模式:一场设计模式的巅峰对决 🥊

122 阅读3分钟

你是否曾在项目中遇到过这样的场景:A一变,B、C、D都要跟着变?这时,设计模式中的“观察者模式”和“发布订阅模式”就像两位武林高手,纷纷出场。今天,我们就用最通俗的语言、最有趣的比喻,带你彻底搞懂这对“孪生兄弟”!


一、概念速览:谁是谁?

观察者模式(Observer Pattern)

"你盯着我,我一动你就跟着动。"

  • 定义:对象间一对多依赖,被观察者状态变化时,自动通知所有观察者。
  • 特点:被观察者直接持有观察者引用,紧耦合。
  • 生活类比:老师点名,学生立刻答到。

发布订阅模式(Publish-Subscribe Pattern)

"你别盯着我,咱们都找小黑板(事件中心)留言,有事小黑板通知大家。"

  • 定义:通过事件中心解耦发布者和订阅者,三者互不认识。
  • 特点:松耦合,灵活扩展。
  • 生活类比:小区公告栏,物业贴通知,业主自取。

二、代码实战:用代码说话

1. 观察者模式

经典实现:属性监听与DOM联动

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>观察者模式示例</title>
</head>
<body>
    <h2>1</h2>
    <button>点击</button>
    <script>
        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', () => {
            obj.count++;
            console.log('当前count:', obj.count);
        });
    </script>
</body>
</html>
  • 解读:属性变化时,直接调用观察者函数,更新DOM。
  • 优点:简单直接,适合小范围、强关联场景。
  • 输出说明 :每点击一次按钮,h2内容和控制台同步更新。
观察者.gif

2. 发布订阅模式

完整事件中心实现

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 kang() { console.log('康买房'); }
function ji() { console.log('季买房'); }
function chen() { console.log('陈买车位'); }

let _event = new EventEmitter();
_event.on('hasHouse', kang);
_event.once('hasHouse', ji);
_event.on('hasCarport', chen);

_event.emit('hasHouse'); // 输出:康买房\n季买房
_event.emit('hasHouse'); // 输出:康买房
_event.emit('hasCarport'); // 输出:陈买车位
  • 解读:事件中心统一管理,发布和订阅互不认识。
  • 优点:解耦,适合复杂、跨模块通信。
  • 输出说明
    • 第一次emit('hasHouse'),康买房、季买房都被调用。
    • 第二次emit('hasHouse'),只有康买房被调用(once只执行一次)。
    • emit('hasCarport'),陈买车位被调用。

三、核心对比:一表胜千言

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

四、幽默总结:

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

五、应用场景举例

  • 观察者模式:Vue响应式、Excel单元格联动
  • 发布订阅模式:Node.js EventEmitter、浏览器自定义事件

六、彩蛋:代码小实验

4.js中的Object.defineProperty妙用

const p = { a: 1 };
let aa = p.a;
Object.defineProperty(p, 'a', {
    get() { return aa; },
    set(value) { aa = value; console.log('set', value); }
});
p.a = 2;
  • 解读:用getter/setter实现属性监听,和观察者思想如出一辙!

七、结语

无论你是“老师点名”派,还是“公告栏通知”派,理解这两种模式的本质,才能在实际开发中游刃有余。下次遇到“消息通知”需求时,别忘了让这两位高手出场哦!😄