← 返回导航

观察者模式

设计模式 · 实战

场景说明

观察者模式定义对象间一对多的依赖:当目标对象(Subject)状态变化时,所有依赖它的观察者(Observer)自动收到通知。前端典型场景包括 Vue 的响应式系统、React 状态更新触发渲染、WebSocket 消息推送、表单联动校验。本 demo 用 TypeScript 接口约束 Subject 与 Observer,实现类型安全的订阅/通知,并对比与事件总线(EventBus)的区别。

完整代码


      
// 观察者模式:Subject 状态变化自动通知 Observer
// 接口约束 + 类型安全的订阅/通知

// 1. 观察者接口:接收通知数据
interface Observer<T> {
  update(data: T): void;
}

// 2. 目标接口:维护观察者列表、增删、通知
interface Subject<T> {
  attach(o: Observer<T>): void;
  detach(o: Observer<T>): void;
  notify(data: T): void;
}

// 3. 通用目标基类:实现订阅管理逻辑
abstract class BaseSubject<T> implements Subject<T> {
  private observers = new Set<Observer<T>>();

  attach(o: Observer<T>): void { this.observers.add(o); }
  detach(o: Observer<T>): void { this.observers.delete(o); }
  notify(data: T): void { this.observers.forEach(function (o) { o.update(data); }); }
}

// 4. 具体目标:价格变动通知
interface PriceChange {
  symbol: string;
  price: number;
  change: number;
}

class StockMarket extends BaseSubject<PriceChange> {
  updatePrice(symbol: string, price: number, prev: number): void {
    this.notify({ symbol: symbol, price: price, change: price - prev });
  }
}

// 5. 具体观察者:显示面板与告警器
class PriceDisplay implements Observer<PriceChange> {
  update(data: PriceChange): void {
    const arrow = data.change >= 0 ? '↑' : '↓';
    console.log('[面板] ' + data.symbol + ' ' + data.price + ' ' + arrow + Math.abs(data.change));
  }
}

class PriceAlert implements Observer<PriceChange> {
  constructor(private threshold: number) {}
  update(data: PriceChange): void {
    if (Math.abs(data.change) >= this.threshold) {
      console.log('[告警] ' + data.symbol + ' 波动 ' + data.change + ' 超过阈值 ' + this.threshold);
    }
  }
}

// 应用
const market = new StockMarket();
const display = new PriceDisplay();
const alert = new PriceAlert(2);

market.attach(display);
market.attach(alert);

market.updatePrice('AAPL', 150, 148);  // 面板+告警都触发
market.updatePrice('GOOG', 2800, 2799.5); // 仅面板触发,波动不足阈值

market.detach(alert);
market.updatePrice('TSLA', 700, 695); // 仅面板触发,告警已取消订阅

关键点解析

使用示例与类型推导


      
// 观察者类型不匹配会被拦截
// class WrongObserver implements Observer<string> { update(s: string) {} }
// market.attach(new WrongObserver());
// 报错:Observer<string> 不能赋值给 Observer<PriceChange>

// 函数式观察者:用对象字面量快速实现
const logger: Observer<PriceChange> = {
  update(data) { console.log('[日志]', JSON.stringify(data)); }
};
market.attach(logger);

// 不同数据类型的 Subject 互不干扰
class TodoStore extends BaseSubject<string[]> {
  private todos: string[] = [];
  add(todo: string) { this.todos.push(todo); this.notify(this.todos); }
}

const store = new TodoStore();
store.attach({ update(list) { console.log('待办更新:', list.length); } });
store.add('学习类型体操');

// 一次性观察者:通知后自动取消订阅
function once<T>(subject: Subject<T>, cb: (data: T) => void) {
  const o: Observer<T> = {
    update(data) { cb(data); subject.detach(o); }
  };
  subject.attach(o);
}
在 Playground 中尝试