#命令模式
#一个撤销功能的噩梦
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 分析命令模式的应用 |