观察者模式定义对象间一对多的依赖:当目标对象(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); // 仅面板触发,告警已取消订阅
Observer<T> / Subject<T>:用泛型参数 T 统一通知数据类型,观察者的 update 参数与目标的 notify 参数类型一致,传错类型编译期拦截。BaseSubject<T> 复用订阅逻辑:把观察者列表管理(attach/detach/notify)抽到抽象基类,具体目标只需关注业务(如 updatePrice),避免重复实现。Set<Observer> 存储观察者,同一观察者重复 attach 自动去重;detach 用 delete 即可。// 观察者类型不匹配会被拦截
// 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);
}