您现在的位置是:网站首页 > 观察者模式(Observer)与发布/订阅的区别文章详情

观察者模式(Observer)与发布/订阅的区别

观察者模式(Observer)和发布/订阅(Pub/Sub)都是用于处理对象间一对多依赖关系的设计模式,但它们在实现方式和耦合度上有显著差异。观察者模式要求观察者直接订阅目标对象,而发布/订阅通过中间代理解耦发布者和订阅者。

观察者模式的核心机制

观察者模式包含两个主要角色:

  • Subject(目标):维护观察者列表,提供添加/删除观察者的方法,状态变化时通知所有观察者
  • Observer(观察者):定义接收通知的更新接口
class Subject {
  constructor() {
    this.observers = [];
  }
  
  addObserver(observer) {
    this.observers.push(observer);
  }
  
  removeObserver(observer) {
    this.observers = this.observers.filter(obs => obs !== observer);
  }
  
  notify(data) {
    this.observers.forEach(observer => observer.update(data));
  }
}

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

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

subject.addObserver(observer1);
subject.addObserver(observer2);
subject.notify('State changed!');  // 两个观察者都会收到通知

发布/订阅模式的实现特点

发布/订阅模式引入事件通道作为中介:

  • Publisher(发布者):将消息发布到特定频道
  • Subscriber(订阅者):向事件通道订阅特定频道
  • Event Channel(事件通道):管理频道与订阅关系
class PubSub {
  constructor() {
    this.channels = {};
  }
  
  subscribe(channel, callback) {
    if (!this.channels[channel]) {
      this.channels[channel] = [];
    }
    this.channels[channel].push(callback);
  }
  
  publish(channel, data) {
    if (this.channels[channel]) {
      this.channels[channel].forEach(callback => callback(data));
    }
  }
}

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

// 订阅者A订阅news频道
pubsub.subscribe('news', data => {
  console.log(`Subscriber A received: ${data}`);
});

// 订阅者B订阅news频道
pubsub.subscribe('news', data => {
  console.log(`Subscriber B received: ${data}`);
});

// 发布消息到news频道
pubsub.publish('news', 'New article published!');

两种模式的关键差异

  1. 耦合程度不同

    • 观察者模式:Subject和Observer相互知晓
    • 发布/订阅:发布者和订阅者通过事件通道完全解耦
  2. 同步机制差异

    • 观察者模式通常是同步的,调用notify()会立即触发所有观察者
    • 发布/订阅可以是异步的,事件通道可以控制消息分发时机
  3. 关系管理方式

    • 观察者模式:Subject直接管理观察者列表
    • 发布/订阅:由独立的事件通道维护订阅关系
  4. 适用场景

    • 观察者模式适合组件间强关联的场景(如React的状态管理)
    • 发布/订阅适合跨系统、跨层级的消息通信(如微前端架构)

实际应用场景对比

观察者模式典型用例:

// Vue响应式系统实现原理简化版
class Dep {
  constructor() {
    this.subscribers = [];
  }
  
  depend() {
    if (target && !this.subscribers.includes(target)) {
      this.subscribers.push(target);
    }
  }
  
  notify() {
    this.subscribers.forEach(sub => sub());
  }
}

let target = null;
function watcher(fn) {
  target = fn;
  fn();
  target = null;
}

const dep = new Dep();
watcher(() => {
  dep.depend();
  console.log('视图更新');
});

dep.notify();  // 触发视图更新

发布/订阅模式典型用例:

// Redux的store实现原理简化版
function createStore(reducer) {
  let state;
  const listeners = [];
  
  const subscribe = listener => {
    listeners.push(listener);
    return () => {
      listeners = listeners.filter(l => l !== listener);
    };
  };
  
  const dispatch = action => {
    state = reducer(state, action);
    listeners.forEach(listener => listener());
  };
  
  const getState = () => state;
  
  dispatch({}); // 初始化state
  
  return { subscribe, dispatch, getState };
}

性能与复杂度考量

  1. 内存消耗

    • 观察者模式:每个Subject维护自己的观察者列表,可能造成重复存储
    • 发布/订阅:集中式事件通道可能成为单点瓶颈
  2. 调试难度

    • 观察者模式:调用链路直接可见,容易跟踪
    • 发布/订阅:消息流转路径不透明,需要额外日志工具
  3. 扩展性

    • 观察者模式:新增观察者类型需要修改Subject接口
    • 发布/订阅:可以动态添加新频道而不影响现有结构

混合使用的情况

现代框架常结合两种模式的优势:

// 混合模式示例:观察者模式实现核心,发布/订阅提供扩展点
class Observable {
  constructor() {
    this._observers = new Set();
    this._eventBus = new PubSub();
  }
  
  subscribe(observer) {
    this._observers.add(observer);
  }
  
  emit(event, data) {
    // 先通知直接观察者
    this._observers.forEach(obs => obs.onEvent(event, data));
    
    // 再通过事件总线广播
    this._eventBus.publish(event, data);
  }
  
  on(event, callback) {
    this._eventBus.subscribe(event, callback);
  }
}

模式选择的决策因素

  1. 系统边界

    • 模块内部通信:观察者模式
    • 跨模块/跨应用通信:发布/订阅
  2. 生命周期管理

    • 需要精细控制订阅关系时:观察者模式
    • 需要松散耦合时:发布/订阅
  3. 性能需求

    • 高频事件:观察者模式(直接调用效率更高)
    • 低频事件:发布/订阅(解耦带来的灵活性更重要)

常见误区和陷阱

  1. 内存泄漏风险

    • 观察者模式:忘记调用removeObserver()
    • 发布/订阅:忘记取消订阅
  2. 过度使用发布/订阅

    // 反模式:滥用事件总线导致"事件地狱"
    eventBus.on('A', () => {
      eventBus.emit('B');
    });
    
    eventBus.on('B', () => {
      eventBus.emit('C');
    });
    
  3. 同步/异步混淆

    • 观察者模式中误用异步通知
    • 发布/订阅中错误假设消息顺序

现代JavaScript中的演进

  1. Observable API

    // RxJS示例
    const observable = new Observable(subscriber => {
      subscriber.next(1);
      setTimeout(() => subscriber.next(2), 1000);
    });
    
    observable.subscribe({
      next: value => console.log(value)
    });
    
  2. Proxy实现的自动观察

    function createObservable(target) {
      const observers = new Set();
      
      return new Proxy(target, {
        set(obj, prop, value) {
          obj[prop] = value;
          observers.forEach(obs => obs());
          return true;
        }
      });
    }
    

框架中的具体实现

  1. React的setState

    • 内部使用类似观察者模式的机制通知组件更新
    • 但通过调度器实现了类似发布/订阅的批处理能力
  2. Vue的响应式系统

    • 每个响应式属性都是独立的Dep(Subject)
    • Watcher作为Observer,通过闭包保持对Dep的引用
  3. Node.js的EventEmitter

    const EventEmitter = require('events');
    const emitter = new EventEmitter();
    
    emitter.on('event', () => console.log('Triggered'));
    emitter.emit('event');
    

我的名片

网名:~川~

岗位:console.log 调试员

坐标:重庆市-九龙坡区

邮箱:cc@qdcc.cn

沙漏人生

站点信息

  • 建站时间:2013/03/16
  • 本站运行
  • 文章数量
  • 总访问量
微信公众号
每次关注
都是向财富自由迈进的一步