Skip to content

命令模式(Command Pattern)笔记

定义

命令模式是一种行为设计模式,它将请求封装为独立的对象,使你可以参数化客户端对象使用不同的请求、队列或日志请求,并支持可撤销的操作。

英文原文定义

The Command Pattern encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

核心概念

命令模式将"动作请求者"与"动作执行者"解耦,通过将请求封装为命令对象,可以像普通对象一样存储、传递和操作这些请求。

主要角色

  1. Command(命令接口):声明执行操作的接口
  2. ConcreteCommand(具体命令):实现命令接口,绑定接收者与动作
  3. Invoker(调用者):要求命令执行请求
  4. Receiver(接收者):知道如何执行与请求相关的操作
  5. Client(客户端):创建具体命令并设置接收者

优缺点

优点

  • 解耦调用者与执行者:调用者无需知道执行细节
  • 可扩展性强:容易添加新命令而不影响现有代码
  • 支持复合命令:可以组合多个简单命令成复杂命令
  • 支持撤销/重做:通过保存命令历史实现
  • 支持事务:可以实现命令的原子操作

缺点

  • 类数量增加:每个命令都需要单独类
  • 可能过度设计:简单操作使用命令模式会增加复杂度
  • 性能开销:间接调用可能带来轻微性能损失

解决的问题

命令模式主要解决以下经典问题:

  1. 行为参数化:需要将操作作为参数传递时
  2. 操作队列:需要实现操作队列或调度时
  3. 撤销/重做:需要支持撤销操作功能时
  4. 事务系统:需要实现原子事务时
  5. GUI操作:菜单项、按钮等需要绑定不同操作时

实现注意事项

  1. 命令粒度

    • 粗粒度:一个命令执行多个操作
    • 细粒度:一个命令执行单一原子操作
  2. 撤销实现

    • 反向操作:执行与命令相反的操作
    • 状态恢复:保存执行前状态并在撤销时恢复
  3. 空命令对象

    • 可作为默认命令或占位符
    • 简化调用者代码(无需判空)
  4. 命令持久化

    • 考虑命令序列化以实现持久化
    • 可用于实现宏命令或脚本系统
  5. 线程安全

    • 多线程环境下命令对象可能需要同步
    • 考虑命令的不可变性设计

实现变体

  1. 简单命令

    • 直接实现命令接口
    • 适用于大多数基本场景
  2. 复合命令(宏命令):

    • 包含多个子命令
    • 按顺序执行一批命令
  3. 异步命令

    • 命令在后台线程执行
    • 需要回调机制通知完成
  4. 持久化命令

    • 可序列化的命令
    • 支持命令的存储和重放

与其他模式的关系

相似模式区分

  1. 策略模式

    • 命令:封装操作和接收者
    • 策略:封装算法,无接收者概念
  2. 职责链模式

    • 命令:明确知道执行者
    • 职责链:不确定哪个对象会处理请求
  3. 备忘录模式

    • 命令:用于实现撤销操作
    • 备忘录:用于保存和恢复对象状态

常见搭配组合

  1. 命令 + 组合:实现宏命令(命令的批处理)
  2. 命令 + 备忘录:实现完善的撤销机制
  3. 命令 + 原型:通过克隆创建命令副本
  4. 命令 + 责任链:实现命令的动态路由

典型应用场景

  • GUI按钮和菜单项操作
  • 事务系统
  • 撤销/重做功能
  • 任务调度系统
  • 远程控制
  • 游戏中的输入处理
  • 批处理脚本

代码示例(文本编辑器实现)

java
// 命令接口
interface Command {
    void execute();
    void undo();
}

// 接收者:文档类
class Document {
    private StringBuilder content = new StringBuilder();
    
    public void write(String text) {
        content.append(text);
    }
    
    public void delete(int length) {
        if (length > content.length()) {
            content.setLength(0);
        } else {
            content.setLength(content.length() - length);
        }
    }
    
    public String getContent() {
        return content.toString();
    }
}

// 具体命令:写入命令
class WriteCommand implements Command {
    private Document document;
    private String text;
    
    public WriteCommand(Document document, String text) {
        this.document = document;
        this.text = text;
    }
    
    @Override
    public void execute() {
        document.write(text);
    }
    
    @Override
    public void undo() {
        document.delete(text.length());
    }
}

// 调用者:菜单项
class MenuItem {
    private Command command;
    
    public void setCommand(Command command) {
        this.command = command;
    }
    
    public void click() {
        command.execute();
    }
}

// 客户端代码
public class TextEditor {
    public static void main(String[] args) {
        Document doc = new Document();
        CommandHistory history = new CommandHistory();
        
        // 创建命令
        Command cmd1 = new WriteCommand(doc, "Hello ");
        Command cmd2 = new WriteCommand(doc, "World!");
        
        // 执行命令
        cmd1.execute();
        history.push(cmd1);
        
        cmd2.execute();
        history.push(cmd2);
        
        System.out.println(doc.getContent()); // 输出: Hello World!
        
        // 撤销
        Command lastCmd = history.pop();
        if (lastCmd != null) {
            lastCmd.undo();
        }
        
        System.out.println(doc.getContent()); // 输出: Hello 
    }
}

// 简单的命令历史记录
class CommandHistory {
    private Stack<Command> history = new Stack<>();
    
    public void push(Command cmd) {
        history.push(cmd);
    }
    
    public Command pop() {
        if (history.isEmpty()) {
            return null;
        }
        return history.pop();
    }
}