TypeScript设计模式:观察者模式

观察者模式(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模式)和发布-订阅场景,如界面组件响应用户操作、物联网设备监控传感器变化、代理观察工具结果以决策,以及消息队列和事件总线系统。

相关推荐
干就完了12 小时前
js对象常用方法都在这,使用时想不到?不存在的
前端·javascript
艾小码2 小时前
还在硬邦邦跳转页面?Vue这3招让应用丝滑如德芙!
前端·javascript·vue.js
RoyLin2 小时前
TypeScript设计模式:备忘录模式
前端·后端·typescript
阿笑带你学前端2 小时前
Flutter本地通知系统:记账提醒的深度实现
前端·flutter
白衣鸽子2 小时前
PageHelper:分页陷阱避免与最佳实践
后端
BingoGo2 小时前
PHP 和 Elasticsearch:给你的应用加个强力搜索引擎
后端·php
泉城老铁2 小时前
Spring Boot对接抖音获取H5直播链接详细指南
spring boot·后端·架构
武子康2 小时前
大数据-101 Spark Streaming 有状态转换详解:窗口操作与状态跟踪实战 附多案例代码
大数据·后端·spark
数据小馒头2 小时前
企业级数据库管理实战(五):多数据库与异构环境的统一管理
后端