TypeScript设计模式:命令模式

8 阅读5分钟

命令模式(Command Pattern)是一种行为型设计模式,通过将请求封装为一个对象(命令),将发出请求的客户端与执行请求的接收者解耦。命令对象可以存储操作的参数、支持撤销/重做、队列化或日志记录等功能,增强系统的灵活性和扩展性。

设计模式原理

命令模式的核心是将请求封装为命令对象,包含执行所需的所有信息。客户端创建命令并传递给调用者(Invoker),调用者触发命令的执行,接收者(Receiver)处理具体逻辑。命令模式通过解耦客户端和接收者,支持灵活的操作管理和扩展。

结构

  • 命令接口(Command):定义命令的执行接口。
  • 具体命令(ConcreteCommand):实现命令接口,绑定接收者和操作。
  • 调用者(Invoker):持有命令对象,触发执行。
  • 接收者(Receiver):执行具体业务逻辑。
  • 客户端(Client):创建命令并设置调用者。

优点

  • 解耦:客户端与接收者分离,降低耦合度。
  • 扩展性:易于添加新命令,无需修改现有代码。
  • 可撤销/重做:命令对象可存储状态,支持撤销操作。
  • 可组合:支持命令队列、事务或宏命令。

缺点

  • 类爆炸:大量命令可能导致类数量增加。
  • 复杂性增加:命令和调用者引入额外层,增加代码复杂度。
  • 内存开销:存储大量命令对象可能占用内存。
  • 调试难度:命令链较长时,追踪逻辑可能复杂。

适用场景

  • 操作封装:如任务管理、事务处理。
  • 撤销/重做:如文本编辑器、图形工具。
  • 异步处理:如命令队列、事件驱动系统。
  • CQRS架构:如Nest.js中的命令分发。

TypeScript 实现示例

我们实现一个任务管理系统,使用Nest.js的CQRS模块,通过命令模式处理任务的创建和更新。命令封装任务操作,CommandBus分发命令,TaskService处理业务逻辑。代码使用TypeScript和Nest.js框架,确保类型安全和模块化。

项目结构

task-manager/
├── src/
│   ├── tasks/
│   │   ├── commands/
│   │   │   ├── create-task.command.ts
│   │   │   ├── update-task.command.ts
│   │   ├── handlers/
│   │   │   ├── create-task.handler.ts
│   │   │   ├── update-task.handler.ts
│   │   ├── task.service.ts
│   │   ├── tasks.controller.ts
│   │   ├── tasks.module.ts
│   ├── app.module.ts
│   ├── main.ts
├── tsconfig.json
├── package.json

1. 安装依赖

npm init -y
npm install @nestjs/core @nestjs/common @nestjs/cqrs rxjs typescript @types/node
npx tsc --init

配置 tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "commonjs",
    "strict": true,
    "moduleResolution": "node",
    "outDir": "./dist",
    "rootDir": "./src"
  }
}

配置 package.json

{
  "scripts": {
    "start": "npx ts-node src/main.ts"
  }
}

2. 定义命令 (tasks/commands/create-task.command.ts)

export class CreateTaskCommand {
  constructor(
    public readonly id: string,
    public readonly title: string,
    public readonly description: string
  ) {}
}

说明CreateTaskCommand封装创建任务的请求,包含任务ID、标题和描述。

3. 定义命令 (tasks/commands/update-task.command.ts)

export class UpdateTaskCommand {
  constructor(
    public readonly id: string,
    public readonly title: string,
    public readonly description: string
  ) {}
}

说明UpdateTaskCommand封装更新任务的请求,包含更新的任务信息。

4. 定义命令处理程序 (tasks/handlers/create-task.handler.ts)

import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { TaskService } from '../task.service';
import { CreateTaskCommand } from '../commands/create-task.command';

@CommandHandler(CreateTaskCommand)
export class CreateTaskHandler implements ICommandHandler<CreateTaskCommand> {
  constructor(private readonly taskService: TaskService) {}

  async execute(command: CreateTaskCommand): Promise<void> {
    const { id, title, description } = command;
    console.log(`处理命令:创建任务,ID=${id}, 标题="${title}"`);
    await this.taskService.createTask(id, title, description);
  }
}

说明CreateTaskHandler作为接收者,处理CreateTaskCommand,调用TaskService执行创建逻辑。

5. 定义命令处理程序 (tasks/handlers/update-task.handler.ts)

import { CommandHandler, ICommandHandler } from '@nestjs/cqrs';
import { TaskService } from '../task.service';
import { UpdateTaskCommand } from '../commands/update-task.command';

@CommandHandler(UpdateTaskCommand)
export class UpdateTaskHandler implements ICommandHandler<UpdateTaskCommand> {
  constructor(private readonly taskService: TaskService) {}

  async execute(command: UpdateTaskCommand): Promise<void> {
    const { id, title, description } = command;
    console.log(`处理命令:更新任务,ID=${id}, 标题="${title}"`);
    await this.taskService.updateTask(id, title, description);
  }
}

说明UpdateTaskHandler处理UpdateTaskCommand,调用TaskService执行更新逻辑。

6. 定义接收者 (tasks/task.service.ts)

import { Injectable } from '@nestjs/common';

@Injectable()
export class TaskService {
  private tasks: Map<string, { title: string; description: string }> = new Map();

  async createTask(id: string, title: string, description: string): Promise<void> {
    console.log(`任务服务:创建任务,ID=${id}, 标题="${title}", 描述="${description}"`);
    this.tasks.set(id, { title, description });
  }

  async updateTask(id: string, title: string, description: string): Promise<void> {
    if (!this.tasks.has(id)) {
      console.log(`任务服务:任务不存在,ID=${id}`);
      throw new Error('任务不存在');
    }
    console.log(`任务服务:更新任务,ID=${id}, 标题="${title}", 描述="${description}"`);
    this.tasks.set(id, { title, description });
  }

  async getTask(id: string): Promise<{ title: string; description: string } | undefined> {
    return this.tasks.get(id);
  }
}

说明TaskService作为接收者,处理实际的任务创建和更新逻辑,存储任务数据。

7. 定义控制器 (tasks/tasks.controller.ts)

import { Controller, Post, Body } from '@nestjs/common';
import { CommandBus } from '@nestjs/cqrs';
import { CreateTaskCommand } from './commands/create-task.command';
import { UpdateTaskCommand } from './commands/update-task.command';

@Controller('tasks')
export class TasksController {
  constructor(private readonly commandBus: CommandBus) {}

  @Post('create')
  async createTask(@Body() body: { id: string; title: string; description: string }): Promise<void> {
    console.log(`控制器:接收创建任务请求,ID=${body.id}`);
    const command = new CreateTaskCommand(body.id, body.title, body.description);
    await this.commandBus.execute(command);
  }

  @Post('update')
  async updateTask(@Body() body: { id: string; title: string; description: string }): Promise<void> {
    console.log(`控制器:接收更新任务请求,ID=${body.id}`);
    const command = new UpdateTaskCommand(body.id, body.title, body.description);
    await this.commandBus.execute(command);
  }
}

说明TasksController作为客户端,创建命令并通过CommandBus分发。

8. 定义模块 (tasks/tasks.module.ts)

import { Module } from '@nestjs/common';
import { CqrsModule } from '@nestjs/cqrs';
import { TaskService } from './task.service';
import { TasksController } from './tasks.controller';
import { CreateTaskHandler } from './handlers/create-task.handler';
import { UpdateTaskHandler } from './handlers/update-task.handler';

@Module({
  imports: [CqrsModule],
  controllers: [TasksController],
  providers: [TaskService, CreateTaskHandler, UpdateTaskHandler],
})
export class TasksModule {}

说明TasksModule集成CQRS模块,注册服务和处理程序。

9. 定义根模块 (app.module.ts)

import { Module } from '@nestjs/common';
import { TasksModule } from './tasks/tasks.module';

@Module({
  imports: [TasksModule],
})
export class AppModule {}

10. 入口文件 (main.ts)

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  await app.listen(3000);
  console.log('应用启动在 http://localhost:3000');
}
bootstrap();

11. 编译与运行

npx tsc
npm start

12. 测试示例

使用工具(如Postman)发送HTTP请求测试:

  • 创建任务

    POST http://localhost:3000/tasks/create
    Content-Type: application/json
    {
      "id": "task1",
      "title": "完成报告",
      "description": "撰写年度报告"
    }
    
  • 更新任务

    POST http://localhost:3000/tasks/update
    Content-Type: application/json
    {
      "id": "task1",
      "title": "完成修订报告",
      "description": "修订年度报告"
    }
    

运行后,控制台输出类似:

应用启动在 http://localhost:3000
控制器:接收创建任务请求,ID=task1
处理命令:创建任务,ID=task1, 标题="完成报告"
任务服务:创建任务,ID=task1, 标题="完成报告", 描述="撰写年度报告"
控制器:接收更新任务请求,ID=task1
处理命令:更新任务,ID=task1, 标题="完成修订报告"
任务服务:更新任务,ID=task1, 标题="完成修订报告", 描述="修订年度报告"

解释输出

  • 控制器创建命令并通过CommandBus分发。
  • CommandBus作为调用者,触发命令的execute方法。
  • 命令处理程序调用TaskService(接收者)执行具体逻辑。
  • 客户端(控制器)与接收者(服务)完全解耦。

总结

命令模式的优点在于其解耦、扩展性、可撤销/重做和可组合性。客户端与接收者分离,降低耦合;易于添加新命令;命令对象可存储状态,支持撤销;支持命令队列和事务。该模式特别适用于操作封装、撤销/重做、异步处理和CQRS架构场景,如Nest.js中的任务管理、事件驱动系统、文本编辑器和事务处理系统。