命令模式(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中的任务管理、事件驱动系统、文本编辑器和事务处理系统。