命令模式

一个撤销功能的噩梦

2021年,我们团队要给编辑器加"撤销/重做"功能。

最初的做法:

// 每个操作直接修改状态
class Editor {
    String text = "";

    void insert(int pos, String str) {
        text = text.substring(0, pos) + str + text.substring(pos);
    }

    void delete(int pos, int len) {
        text = text.substring(0, pos) + text.substring(pos + len);
    }

    void replace(int pos, int len, String str) {
        text = text.substring(0, pos) + str + text.substring(pos + len);
    }
}

要撤销?根本没法做——不知道之前是什么。

命令模式解决的就是这个问题:把操作封装成对象,这样就可以记录操作历史、支持撤销重做。


二、命令模式核心🔴

2.1 基本结构

// 命令接口
interface Command {
    void execute();
    void undo();  // 支持撤销
}

// 具体命令
class InsertCommand implements Command {
    private final Editor editor;
    private final int position;
    private final String text;
    private String deletedText; // 记录撤销时需要的信息

    public InsertCommand(Editor editor, int position, String text) {
        this.editor = editor;
        this.position = position;
        this.text = text;
    }

    @Override
    public void execute() {
        deletedText = editor.getText().substring(position, position + text.length());
        editor.insert(position, text);
    }

    @Override
    public void undo() {
        editor.delete(position, text.length());
    }
}

class DeleteCommand implements Command {
    private final Editor editor;
    private final int position;
    private final int length;
    private String deletedText; // 保存被删除的文本

    @Override
    public void execute() {
        deletedText = editor.getText().substring(position, position + length);
        editor.delete(position, length);
    }

    @Override
    public void undo() {
        editor.insert(position, deletedText);
    }
}

2.2 命令调用者

class CommandManager {
    private final Stack<Command> undoStack = new Stack<>();
    private final Stack<Command> redoStack = new Stack<>();

    public void executeCommand(Command command) {
        command.execute();
        undoStack.push(command);
        redoStack.clear(); // 新命令清除重做栈
    }

    public void undo() {
        if (undoStack.isEmpty()) return;

        Command command = undoStack.pop();
        command.undo();
        redoStack.push(command);
    }

    public void redo() {
        if (redoStack.isEmpty()) return;

        Command command = redoStack.pop();
        command.execute();
        undoStack.push(command);
    }
}

2.3 使用方式

Editor editor = new Editor();
CommandManager manager = new CommandManager();

// 执行命令
manager.executeCommand(new InsertCommand(editor, 0, "Hello"));
manager.executeCommand(new InsertCommand(editor, 5, " World"));
manager.executeCommand(new DeleteCommand(editor, 5, 6));

// 撤销
manager.undo();
manager.undo();

// 重做
manager.redo();

三、命令队列🟡

3.1 异步命令执行

class CommandQueue {
    private final BlockingQueue<Command> queue = new LinkedBlockingQueue<>();
    private final ExecutorService executor = Executors.newFixedThreadPool(4);

    public void enqueue(Command command) {
        queue.offer(command);
    }

    public void start() {
        for (int i = 0; i < 4; i++) {
            executor.submit(() -> {
                while (true) {
                    try {
                        Command command = queue.take();
                        command.execute();
                    } catch (InterruptedException e) {
                        break;
                    }
                }
            });
        }
    }
}

3.2 宏命令

class MacroCommand implements Command {
    private final List<Command> commands = new ArrayList<>();

    public void add(Command command) {
        commands.add(command);
    }

    @Override
    public void execute() {
        for (Command command : commands) {
            command.execute();
        }
    }

    @Override
    public void undo() {
        for (int i = commands.size() - 1; i >= 0; i--) {
            commands.get(i).undo();
        }
    }
}

// 使用:批量操作作为单个命令
MacroCommand batch = new MacroCommand();
batch.add(new InsertCommand(editor, 0, "A"));
batch.add(new InsertCommand(editor, 1, "B"));
batch.add(new InsertCommand(editor, 2, "C"));
manager.executeCommand(batch); // 一键执行三个操作
manager.undo(); // 一键撤销三个操作

四、Spring 中的命令模式🟡

4.1 Spring JDBC 的命令模式

// JdbcTemplate.execute 实际上是命令模式的变体
jdbcTemplate.execute((Connection connection) -> {
    // Command: 在连接上执行操作
    PreparedStatement ps = connection.prepareStatement(sql);
    ps.setString(1, "value");
    ps.executeUpdate();
    ps.close();
    return null;
});

4.2 事务模板

// TransactionTemplate:命令模式的异步/可复用版本
TransactionTemplate template = new TransactionTemplate(transactionManager);

template.execute(status -> {
    // command.execute()
    userDao.save(user);
    orderDao.save(order);
    return null;
});

五、面试总结

级别期望回答
P5能写出命令模式的基本结构
P6能说出撤销/重做的实现方式
P7能结合 Spring 分析命令模式的应用