1. 设计模式的前世今生
想象你正在组装乐高玩具。当你遇到重复的拼装步骤时,肯定会想要找到某种"固定套路"来提高效率。在编程世界里,设计模式就是这样的"拼装秘籍"。
在Node.js的异步王国里,有两位重量级选手特别值得关注:观察者模式就像敏锐的新闻记者,时刻捕捉事件动态;中介者模式则像老练的外交官,在复杂对象间斡旋调停。而它们共同的舞台,就是Node.js自带的事件系统。
2. 观察者模式的深度剖析
2.1 基础版观察者实现
// Node.js原生实现(技术栈:Node.js 18+)
class NewsPublisher {
constructor() {
this.subscribers = new Map();
}
// 订阅特定新闻类型
subscribe(type, callback) {
if (!this.subscribers.has(type)) {
this.subscribers.set(type, new Set());
}
this.subscribers.get(type).add(callback);
}
// 取消订阅
unsubscribe(type, callback) {
if (this.subscribers.has(type)) {
this.subscribers.get(type).delete(callback);
}
}
// 发布新闻
publish(type, content) {
if (this.subscribers.has(type)) {
this.subscribers.get(type).forEach(callback => {
callback(content);
});
}
}
}
// 使用示例
const CNN = new NewsPublisher();
// 体育新闻订阅者
const sportsFan = news => console.log(`[体育迷] 收到消息: ${news}`);
CNN.subscribe('sports', sportsFan);
// 突发新闻订阅
CNN.subscribe('breaking', news =>
console.log(`[紧急通知] ${new Date().toISOString()} ${news}`));
// 发布不同类型消息
CNN.publish('sports', '湖人队夺得总冠军');
CNN.publish('breaking', '国际空间站发现新型微生物');
2.2 模式进阶改造
在实际项目中,我们往往需要更精细的控制:
class EnhancedPublisher extends NewsPublisher {
// 添加订阅次数统计
subscribe(type, callback) {
super.subscribe(type, callback);
console.log(`订阅成功,当前${type}类型订阅数:${this.subscribers.get(type).size}`);
}
// 带优先级的发布控制
publishWithPriority(type, content, level = 'normal') {
const callbacks = Array.from(this.subscribers.get(type) || []);
if (level === 'urgent') {
callbacks.reverse(); // 紧急消息倒序执行
}
callbacks.forEach(cb => cb(content));
}
}
// 验证优先级功能
const BBC = new EnhancedPublisher();
BBC.subscribe('alert', m => console.log('普通处理:', m));
BBC.subscribe('alert', m => console.log('优先处理:', m));
BBC.publishWithPriority('alert', '服务器过载警告', 'urgent');
2.3 应用场景与注意要点
这个模式在以下场景大显身手:
- WebSocket消息推送系统
- 跨组件状态同步(如Redux)
- 实时数据监控仪表盘
需要特别注意的是:
- 避免循环通知(在事件处理中触发新事件)
- 异步回调可能导致执行顺序不可控
- 内存泄漏风险(长期持有回调引用)
3. 中介者模式的精妙舞步
3.1 聊天室实战案例
// 聊天室实现(技术栈:Node.js 18+)
class ChatMediator {
constructor() {
this.users = new Map();
this.messageHistory = [];
}
register(user) {
this.users.set(user.id, user);
user.setMediator(this);
}
sendMessage(senderId, content) {
const timestamp = new Date().toISOString();
const message = { senderId, content, timestamp };
// 历史记录存储
this.messageHistory.push(message);
// 广播给其他用户
this.users.forEach(user => {
if (user.id !== senderId) {
user.receive(message);
}
});
}
}
class ChatUser {
constructor(id) {
this.id = id;
this.mediator = null;
}
setMediator(mediator) {
this.mediator = mediator;
}
send(content) {
console.log(`${this.id} 发送消息...`);
this.mediator.sendMessage(this.id, content);
}
receive(message) {
console.log(`${this.id} 收到来自${message.senderId}的消息: ${message.content}`);
}
}
// 创建聊天室
const chatRoom = new ChatMediator();
const alice = new ChatUser('Alice');
const bob = new ChatUser('Bob');
// 注册用户
chatRoom.register(alice);
chatRoom.register(bob);
// 消息交互
alice.send('今晚团建?');
bob.send('好的,几点集合?');
3.2 模式变体与优化
在复杂系统中可以扩展更多功能:
class AdvancedMediator extends ChatMediator {
// 添加消息过滤
sendMessage(senderId, content) {
if (this.isSensitive(content)) {
console.log(`检测到敏感词: ${senderId}`);
return;
}
super.sendMessage(senderId, content);
}
isSensitive(text) {
const forbiddenWords = ['赌博', '暴力'];
return forbiddenWords.some(word => text.includes(word));
}
}
// 测试敏感词过滤
const safeChat = new AdvancedMediator();
safeChat.register(new ChatUser('Charlie'));
safeChat.sendMessage('Charlie', '这个赌博网站...'); // 触发过滤
3.3 适用场景与优劣分析
该模式特别适合:
- 多方通信的物联网系统
- 企业级审批流程系统
- 分布式服务协调
优点显而易见:
- 简化对象间网状依赖
- 集中控制通信逻辑
- 便于扩展新功能
但也需注意:
- 中介者可能成为性能瓶颈
- 过度中心化可能违反SRP原则
4. Node.js事件系统的内核奥秘
4.1 EventEmitter魔法揭秘
// Node.js原生事件模块(技术栈:Node.js 18+)
const EventEmitter = require('events');
const util = require('util');
// 自定义温度传感器
class TemperatureSensor extends EventEmitter {
constructor() {
super();
this.currentTemp = 25;
setInterval(() => this.checkTemperature(), 1000);
}
checkTemperature() {
const fluctuation = Math.random() * 2 - 1;
this.currentTemp += fluctuation;
if (this.currentTemp > 28) {
this.emit('overheat', this.currentTemp);
} else if (this.currentTemp < 22) {
this.emit('undercool', this.currentTemp);
}
}
}
// 创建监控系统
const sensor = new TemperatureSensor();
sensor.on('overheat', temp =>
console.error(`温度过高告警:${temp.toFixed(1)}℃`));
sensor.on('undercool', temp =>
console.warn(`低温警告:${temp.toFixed(1)}℃`));
// 添加一次性监听器
sensor.once('overheat', () =>
console.log('首次触发高温警报'));
4.2 高级事件控制技巧
// 带流量控制的事件发射器
class ThrottledEmitter extends EventEmitter {
constructor(interval = 1000) {
super();
this.lastEmit = Date.now();
this.interval = interval;
}
emit(event, ...args) {
const now = Date.now();
if (now - this.lastEmit >= this.interval) {
super.emit(event, ...args);
this.lastEmit = now;
}
}
}
// 测试频率控制
const controlledEmitter = new ThrottledEmitter(2000);
controlledEmitter.on('ping', () => console.log('收到ping'));
setInterval(() => {
controlledEmitter.emit('ping');
}, 500); // 实际每2秒才会触发一次
4.3 系统机制深度解析
Node.js的事件循环就像精密的瑞士钟表:
- 定时器阶段:处理setTimeout/setInterval
- I/O回调阶段:处理网络请求等异步操作
- 闲置/准备阶段:内部使用
- 轮询阶段:检索新的I/O事件
- 检查阶段:执行setImmediate回调
- 关闭事件:处理socket关闭等
在编写事件监听时要注意:
- 避免在高峰时产生"雪崩效应"
- 及时移除无用监听器(memory leak)
- 合理使用同步/异步事件触发
5. 综合对比与选型指南
5.1 模式间的血缘关系
观察者模式与事件系统的关系,就像手写信和电子邮件:
- EventEmitter是观察者模式的标准化实现
- 中介者模式常结合观察者模式使用
- 事件系统具备更完善的管理功能
5.2 选型决策树
当遇到通信需求时,可参考以下流程:
- 通信双方是否直接相关?→ 否 → 中介者模式
- 需要一对多通知机制?→ 是 → 观察者模式
- 需要完善的事件管理?→ 是 → EventEmitter
5.3 最佳实践原则
- 模块化通信:保持事件类型的命名空间化
- 防御式编程:总是处理error事件
- 性能监控:使用emitter.listeners()检查监听器数量
- 错误隔离:用domain模块或process.on('uncaughtException')
6. 模式启示录:未来与演进
随着TypeScript的普及,类型安全的事件系统开始流行:
// 类型安全的Emitter示例
interface AppEvents {
login: (userId: string) => void;
logout: (reason: string) => void;
}
class TypedEmitter extends EventEmitter {
emit<K extends keyof AppEvents>(
event: K,
...args: Parameters<AppEvents[K]>
): boolean {
return super.emit(event, ...args);
}
}
微服务架构下,这些模式正在向分布式形态演进:
- 观察者模式 → 消息队列(RabbitMQ/Kafka)
- 中介者模式 → 服务网格(Istio/Linkerd)
- 事件系统 → 服务端事件(SSE/WebSocket)