观察者模式(Observer Pattern)是一种行为型设计模式,通过定义对象间的一对多依赖关系,当一个对象(主题,Subject)的状态发生变化时,所有依赖它的对象(观察者,Observer)都会被自动通知并更新。这种模式通过解耦发布者和订阅者,广泛应用于实时响应场景,如事件处理、界面更新和人工智能系统。
设计模式原理
观察者模式的核心是主题(Subject)维护一个观察者(Observer)列表,当状态变化时通过通知方法广播更新。观察者实现更新接口,处理通知。这种机制确保主题和观察者松耦合,易于扩展和维护。
结构
- 主题(Subject):维护观察者列表,提供添加、移除、通知方法。
- 具体主题(ConcreteSubject):实现主题,存储状态并触发通知。
- 观察者(Observer):定义更新接口,处理主题的通知。
- 具体观察者(ConcreteObserver):实现观察者,定义具体更新逻辑。
优点
- 松耦合:主题和观察者无需直接引用,便于独立开发和测试。
- 动态订阅:支持运行时添加或移除观察者,灵活性高。
- 广播机制:一个状态变化可通知多个观察者,适合事件分发。
- 可复用:观察者逻辑可复用于不同主题。
缺点
- 内存风险:未移除的观察者可能导致内存泄漏。
- 性能开销:大量观察者时,通知可能影响效率。
- 通知顺序:观察者执行顺序不固定,可能需额外控制。
- 调试复杂:异步通知链长时,追踪问题较难。
适用场景
- 事件处理:如界面组件响应用户操作(点击、输入)。
- 实时系统:如物联网设备监控传感器变化。
- 人工智能代理:如ReAct模式中,代理观察工具结果以决策。
- 发布-订阅:如消息队列、事件总线。
在ReAct智能体中,工具执行器(主题)异步返回结果,智能体(观察者)观察并迭代"推理-行动-观察"循环,解决复杂任务。
TypeScript 实现示例
我们实现一个ReAct模式的智能体,智能体通过观察工具执行结果(如搜索)动态调整决策,回答问题。
项目结构
go
react-agent/
├── src/
│ ├── observer.ts // 核心接口
│ ├── tool.ts // 工具(主题)
│ ├── agent.ts // 代理(观察者)
│ ├── main.ts // 执行示例
├── tsconfig.json
├── package.json
1. 安装依赖
bash
npm init -y
npm install typescript @types/node
npx tsc --init
配置 tsconfig.json
:
json
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"strict": true,
"moduleResolution": "node"
}
}
2. 定义接口 (observer.ts)
typescript
interface Observation {
action: string;
result: string;
step: number;
}
export interface ReActObserver {
update(observation: Observation): void;
}
export interface ToolSubject {
attach(observer: ReActObserver): void;
detach(observer: ReActObserver): void;
notify(observation: Observation): void;
execute(action: string): Promise<void>;
}
3. 实现工具 (tool.ts)
typescript
import { ToolSubject, ReActObserver, Observation } from './observer';
export class WebSearchTool implements ToolSubject {
private observers: ReActObserver[] = [];
attach(observer: ReActObserver): void {
if (!this.observers.includes(observer)) {
this.observers.push(observer);
console.log('代理已订阅工具');
}
}
detach(observer: ReActObserver): void {
const index = this.observers.indexOf(observer);
if (index > -1) {
this.observers.splice(index, 1);
console.log('代理已取消订阅工具');
}
}
notify(observation: Observation): void {
this.observers.forEach(observer => observer.update(observation));
}
async execute(action: string): Promise<void> {
await new Promise(resolve => setTimeout(resolve, 1000));
const result = `搜索"${action}"的结果:已检索到相关信息。`;
const observation: Observation = {
action,
result,
step: Date.now()
};
console.log(`工具执行:${action} -> ${result}`);
this.notify(observation);
}
}
4. 实现ReAct代理 (agent.ts)
typescript
import { ReActObserver, ToolSubject, Observation } from './observer';
export class ReActAgent implements ReActObserver {
private thoughts: string[] = [];
private observations: Observation[] = [];
private maxSteps = 5;
private currentStep = 0;
private finalAnswer: string | null = null;
constructor(private task: string, private tool: ToolSubject) {
this.tool.attach(this);
this.thinkAndAct();
}
private async thinkAndAct(): Promise<void> {
while (this.currentStep < this.maxSteps && !this.finalAnswer) {
const thought = this.generateThought();
this.thoughts.push(thought);
console.log(`步骤 ${this.currentStep + 1} - 推理:${thought}`);
const action = this.generateAction(thought);
console.log(`步骤 ${this.currentStep + 1} - 行动:${action}`);
await this.tool.execute(action);
this.currentStep++;
await new Promise(resolve => {
const check = () => {
if (this.observations.length > this.currentStep - 1 || this.finalAnswer) {
resolve(null);
} else {
setTimeout(check, 100);
}
};
check();
});
}
if (this.finalAnswer) {
console.log(`最终答案:${this.finalAnswer}`);
} else {
this.finalAnswer = '达到最大步骤,总结观察结果。';
console.log(`最终答案:${this.finalAnswer}`);
}
}
private generateThought(): string {
const prevObs = this.observations[this.currentStep - 1]?.result || '';
return `基于任务"${this.task}"和前次观察:"${prevObs.substring(0, 50)}...",需要深入搜索。`;
}
private generateAction(thought: string): string {
return `搜索:${this.task} 步骤 ${this.currentStep + 1}`;
}
update(observation: Observation): void {
this.observations.push(observation);
console.log(`步骤 ${this.currentStep} - 观察:${observation.result}`);
if (observation.result.includes('已检索到相关信息') && this.currentStep === 3) {
this.finalAnswer = `已解决:${this.task},结果:${observation.result}`;
}
}
cleanup(): void {
this.tool.detach(this);
}
}
5. 运行示例 (main.ts)
typescript
import { WebSearchTool } from './tool';
import { ReActAgent } from './agent';
const tool = new WebSearchTool();
const agent = new ReActAgent('中国的首都是哪里?', tool);
console.log('ReAct代理启动,任务:中国的首都是哪里?');
setTimeout(() => {
agent.update({ action: '搜索:中国的首都是哪里? 步骤 1', result: '北京是首都。', step: 1 });
}, 2000);
setTimeout(() => {
agent.cleanup();
console.log('代理已清理');
}, 10000);
6. 编译与运行
bash
npx tsc
node dist/main.js
运行后,控制台输出类似:
rust
ReAct代理启动,任务:中国的首都是哪里?
代理已订阅工具
步骤 1 - 推理:基于任务"中国的首都是哪里?"和前次观察:"",需要深入搜索。
步骤 1 - 行动:搜索:中国的首都是哪里? 步骤 1
工具执行:搜索:中国的首都是哪里? 步骤 1 -> 搜索"搜索:中国的首都是哪里? 步骤 1"的结果:已检索到相关信息。
步骤 1 - 观察:搜索"搜索:中国的首都是哪里? 步骤 1"的结果:已检索到相关信息。
...
最终答案:已解决:中国的首都是哪里?,结果:搜索"搜索:中国的首都是哪里? 步骤 3"的结果:已检索到相关信息。
代理已清理
总结
观察者模式的优点在于其松耦合、动态订阅、广播机制和可复用性。主题和观察者无需直接引用,使得开发和测试更加独立;运行时可动态添加或移除观察者,灵活性高;状态变化可通知多个观察者,适合事件分发;观察者逻辑可复用于不同主题。该模式特别适用于事件处理、实时系统、人工智能代理(如ReAct模式)和发布-订阅场景,如界面组件响应用户操作、物联网设备监控传感器变化、代理观察工具结果以决策,以及消息队列和事件总线系统。