命令模式深度指南:封装请求与解耦行为的艺术
命令模式是一种行为型设计模式,它将请求封装为对象,从而允许您参数化客户端具有不同请求、排队或记录请求,以及支持可撤销的操作。
📖 什么是命令模式?
命令模式(Command Pattern)是一种行为型设计模式,其核心思想是将请求封装为对象,从而使您能够:
- ✨ 参数化不同的请求
- ✨ 队列化管理请求
- ✨ 记录请求日志
- ✨ 支持撤销/重做操作
🏗️ 命令模式的组成要素
┌─────────────────────────────────────────────────────────────┐
│ 命令模式架构 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ Client │─────▶│ Invoker │─────▶│ Command │ │
│ │ 客户端 │ │ 调用者 │ │ (抽象命令) │ │
│ └──────────┘ └──────────┘ └────────┬─────────┘ │
│ │ │
│ ┌──────────┐ │ │
│ │ Receiver │◀─────────────────┤ │
│ │ 接收者 │ │ │
│ └──────────┘ ┌─────────────┴─────────┐ │
│ │ │ │
│ ┌─────────▼─────────┐ ┌────────▼┐ │
│ │ ConcreteCommand │ │Concrete │ │
│ │ (具体命令) │ │Command │ │
│ └─────────────────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────────┘
核心角色
| 角色 | 职责 | 示例 |
|---|---|---|
| Command (抽象命令) | 定义执行操作的接口 |
Command 接口 |
| ConcreteCommand (具体命令) | 绑定具体操作到接收者 |
LightOnCommand, LightOffCommand
|
| Invoker (调用者) | 发起命令请求 | RemoteController |
| Receiver (接收者) | 真正执行操作的对象 |
Light, TV
|
💻 代码示例
TypeScript 实现
// ===== 1. 接收者 (Receiver) =====
class Light {
turnOn(): void {
console.log("💡 灯已打开");
}
turnOff(): void {
console.log("💡 灯已关闭");
}
}
// ===== 2. 抽象命令接口 =====
interface Command {
execute(): void;
undo(): void; // 支持撤销
}
// ===== 3. 具体命令实现 =====
class LightOnCommand implements Command {
private light: Light;
constructor(light: Light) {
this.light = light;
}
execute(): void {
this.light.turnOn();
}
undo(): void {
this.light.turnOff();
}
}
class LightOffCommand implements Command {
private light: Light;
constructor(light: Light) {
this.light = light;
}
execute(): void {
this.light.turnOff();
}
undo(): void {
this.light.turnOn();
}
}
// ===== 4. 调用者 (Invoker) =====
class RemoteController {
private history: Command[] = [];
private currentCommand: Command | null = null;
setCommand(command: Command): void {
this.currentCommand = command;
}
pressButton(): void {
if (this.currentCommand) {
this.currentCommand.execute();
this.history.push(this.currentCommand);
}
}
pressUndo(): void {
if (this.currentCommand) {
this.currentCommand.undo();
}
}
}
// ===== 5. 客户端使用 =====
const light = new Light();
const lightOn = new LightOnCommand(light);
const lightOff = new LightOffCommand(light);
const remote = new RemoteController();
remote.setCommand(lightOn);
remote.pressButton(); // 💡 灯已打开
remote.pressUndo(); // 💡 灯已关闭
🎯 实际应用场景
1️⃣ 图形界面按钮
// 菜单项、按钮都可以复用命令模式
class SaveButton {
private command: Command;
constructor(command: Command) {
this.command = command;
}
click(): void {
this.command.execute();
}
}
2️⃣ 事务日志 & 撤销功能
class TransactionManager {
private commands: Command[] = [];
execute(command: Command): void {
command.execute();
this.commands.push(command);
}
undoAll(): void {
// 倒序撤销所有命令
for (let i = this.commands.length - 1; i >= 0; i--) {
this.commands[i].undo();
}
this.commands = [];
}
}
3️⃣ 宏命令(批量操作)
class MacroCommand implements Command {
private commands: Command[] = [];
add(command: Command): void {
this.commands.push(command);
}
execute(): void {
this.commands.forEach(cmd => cmd.execute());
}
undo(): void {
this.commands.forEach(cmd => cmd.undo());
}
}
4️⃣ 异步任务队列
class TaskQueue {
private queue: Command[] = [];
addTask(command: Command): void {
this.queue.push(command);
}
async processAll(): Promise<void> {
while (this.queue.length > 0) {
const command = this.queue.shift()!;
await command.executeAsync();
}
}
}
⚖️ 命令模式的优缺点
✅ 优点
| 优点 | 说明 |
|---|---|
| 单一职责 | 将操作封装为独立类,职责清晰 |
| 开闭原则 | 新增命令无需修改现有代码 |
| 解耦调用者与接收者 | 调用者不需要知道谁执行操作 |
| 支持撤销/重做 | 通过 history 轻松实现 |
| 支持日志记录 | 持久化命令用于系统恢复 |
❌ 缺点
| 缺点 | 说明 |
|---|---|
| 类数量增加 | 每个操作都需要一个具体命令类 |
| 复杂度上升 | 需要管理命令队列和历史 |
🆚 对比其他模式
| 模式 | 命令模式 | 策略模式 |
|---|---|---|
| 核心 | 封装请求 | 封装算法 |
| 目的 | 解耦请求发送者与执行者 | 运行时可替换算法 |
| 关注点 | 操作的封装与撤销 | 算法的高效切换 |
| 模式 | 命令模式 | 职责链模式 |
|---|---|---|
| 核心 | 单个请求 | 链式传递 |
| 目的 | 请求被一个对象处理 | 请求被多个对象依次处理 |
📝 总结
命令模式是一种强大且灵活的设计模式,它的核心价值在于:
将"请求"抽象为"对象",从而实现请求的发送者与接收者完全解耦。
这种解耦带来了诸多好处:
- 🔄 支持撤销/重做
- 📝 支持日志记录
- ⏳ 支持异步队列
- 🧩 支持宏命令
在实际开发中,当你需要:
- 实现撤销功能
- 记录操作历史
- 异步处理任务
- 解耦调用逻辑
时,命令模式都是一个值得考虑的选择。
下一期:我们将探讨备忘录模式(Memento Pattern)——如何在不破坏封装性的情况下保存和恢复对象状态。
📚 推荐阅读
本文是「软件架构深度指南」系列文章,更多内容请关注我的主页。
Top comments (0)