DEV Community

架构师小白
架构师小白

Posted on

命令模式深度指南:封装请求与解耦行为的艺术

命令模式深度指南:封装请求与解耦行为的艺术

命令模式是一种行为型设计模式,它将请求封装为对象,从而允许您参数化客户端具有不同请求、排队或记录请求,以及支持可撤销的操作。

📖 什么是命令模式?

命令模式(Command Pattern)是一种行为型设计模式,其核心思想是将请求封装为对象,从而使您能够:

  • ✨ 参数化不同的请求
  • ✨ 队列化管理请求
  • ✨ 记录请求日志
  • ✨ 支持撤销/重做操作

🏗️ 命令模式的组成要素

┌─────────────────────────────────────────────────────────────┐
│                      命令模式架构                            │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   ┌──────────┐      ┌──────────┐      ┌──────────────────┐ │
│   │  Client  │─────▶│ Invoker  │─────▶│    Command       │ │
│   │  客户端  │      │  调用者  │      │    (抽象命令)    │ │
│   └──────────┘      └──────────┘      └────────┬─────────┘ │
│                                                  │            │
│                    ┌──────────┐                  │            │
│                    │ Receiver │◀─────────────────┤            │
│                    │ 接收者   │                  │            │
│                    └──────────┘    ┌─────────────┴─────────┐  │
│                                    │                        │  │
│                          ┌─────────▼─────────┐  ┌────────▼┐ │
│                          │ ConcreteCommand    │  │Concrete │ │
│                          │ (具体命令)          │  │Command  │ │
│                          └─────────────────────┘  └─────────┘ │
└─────────────────────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

核心角色

角色 职责 示例
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();   // 💡 灯已关闭
Enter fullscreen mode Exit fullscreen mode

🎯 实际应用场景

1️⃣ 图形界面按钮

// 菜单项、按钮都可以复用命令模式
class SaveButton {
  private command: Command;

  constructor(command: Command) {
    this.command = command;
  }

  click(): void {
    this.command.execute();
  }
}
Enter fullscreen mode Exit fullscreen mode

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 = [];
  }
}
Enter fullscreen mode Exit fullscreen mode

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());
  }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

⚖️ 命令模式的优缺点

✅ 优点

优点 说明
单一职责 将操作封装为独立类,职责清晰
开闭原则 新增命令无需修改现有代码
解耦调用者与接收者 调用者不需要知道谁执行操作
支持撤销/重做 通过 history 轻松实现
支持日志记录 持久化命令用于系统恢复

❌ 缺点

缺点 说明
类数量增加 每个操作都需要一个具体命令类
复杂度上升 需要管理命令队列和历史

🆚 对比其他模式

模式 命令模式 策略模式
核心 封装请求 封装算法
目的 解耦请求发送者与执行者 运行时可替换算法
关注点 操作的封装与撤销 算法的高效切换
模式 命令模式 职责链模式
核心 单个请求 链式传递
目的 请求被一个对象处理 请求被多个对象依次处理

📝 总结

命令模式是一种强大且灵活的设计模式,它的核心价值在于:

将"请求"抽象为"对象",从而实现请求的发送者与接收者完全解耦。

这种解耦带来了诸多好处:

  • 🔄 支持撤销/重做
  • 📝 支持日志记录
  • ⏳ 支持异步队列
  • 🧩 支持宏命令

在实际开发中,当你需要:

  • 实现撤销功能
  • 记录操作历史
  • 异步处理任务
  • 解耦调用逻辑

时,命令模式都是一个值得考虑的选择。


下一期:我们将探讨备忘录模式(Memento Pattern)——如何在不破坏封装性的情况下保存和恢复对象状态。


📚 推荐阅读


本文是「软件架构深度指南」系列文章,更多内容请关注我的主页。

Top comments (0)