2025/10/10日 每日总结 设计模式实践:命令模式实现可撤销/重做的加法计算器
设计模式实践:命令模式实现可撤销/重做的加法计算器
在需要支持操作回溯(撤销/重做)、日志记录或任务队列的场景中,直接耦合请求发送者和接收者会导致代码扩展性差。命令模式通过将“请求”封装为独立对象,实现请求发送者与接收者的解耦,同时支持对请求的存储、撤销和重做。本文以“支持多次撤销/重做的加法计算器”为例,分享命令模式的设计思想与实践应用。
一、命令模式核心思想
命令模式是一种行为型设计模式,核心目标是封装请求为对象,其关键结构包括:
-
抽象命令(Command):定义执行、撤销、重做的统一接口;
-
具体命令(Concrete Command):实现抽象命令接口,绑定接收者与具体请求逻辑;
-
接收者(Receiver):执行命令对应的具体业务逻辑(如加法运算);
-
调用者(Invoker):持有命令对象,负责触发命令执行、撤销或重做;
-
客户端(Client):创建命令对象并绑定接收者,将命令传递给调用者。
本次实践中,加法计算器的核心需求是“记录操作历史,支持多次撤销和重做”。通过命令模式,每一次加法操作都被封装为命令对象,存储在历史记录中,撤销时反向执行命令,重做时重新执行命令。
二、类图设计
| AbstractCommand | // 抽象命令类
+-------------------+
- execute(value: int): int // 执行命令
- undo(): int // 撤销命令
- redo(): int // 重做命令
+-------------------+
^
|
+-------+-------+
| |
| ConcreteCommand| // 具体命令类
+---------------+
- adder: Adder // 接收者(加法器)
- value: int // 操作数值
+---------------+
- execute(value: int): int // 实现执行逻辑
- undo(): int // 实现撤销逻辑(加负数)
- redo(): int // 实现重做逻辑(重新加原数值)
+---------------+
+-------------------+
| Adder | // 接收者:加法运算核心逻辑
+-------------------+
- num: int // 当前计算结果
+-------------------+
- add(value: int): int // 加法运算,返回最新结果
+-------------------+
+-------------------+
| CalculatorForm | // 调用者:计算器表单(触发命令)
+-------------------+
- command: AbstractCommand // 持有命令对象
+-------------------+
- setCommand(command: AbstractCommand): void // 设置命令
- compute(value: int): void // 触发执行命令
- undo(): void // 触发撤销命令
- redo(): void // 触发重做命令
+-------------------+
三、完整代码实现
1. 接收者:Adder(加法器)
负责核心的加法运算,维护当前计算结果,不关心命令的撤销/重做逻辑。
/**
* 接收者类:加法器
* 实现核心加法运算,提供数值累加功能
*/
public class Adder {
private int num = 0; // 存储当前计算结果
/**
* 加法运算:将输入值累加到当前结果
* @param value 待加数值
* @return 累加后的结果
*/
public int add(int value) {
num += value;
return num;
}
}
2. 抽象命令:AbstractCommand
定义命令的统一接口,包含执行、撤销、重做三个核心方法,约束具体命令的行为。
/**
* 抽象命令类
* 定义命令的核心接口:执行、撤销、重做
*/
public abstract class AbstractCommand {
/**
* 执行命令
* @param value 操作数值
* @return 执行后的结果
*/
public abstract int execute(int value);
/**
* 撤销命令
* @return 撤销后的结果
*/
public abstract int undo();
/**
* 重做命令
* @return 重做后的结果
*/
public abstract int redo();
}
3. 具体命令:ConcreteCommand
实现抽象命令接口,绑定接收者(Adder),封装执行、撤销、重做的具体逻辑。
/**
* 具体命令类:加法命令
* 绑定加法器,实现加法操作的执行、撤销和重做
*/
public class ConcreteCommand extends AbstractCommand {
private Adder adder = new Adder(); // 持有接收者实例
private int value; // 存储当前执行的操作数值
/**
* 执行命令:调用加法器的加法运算
* @param value 待加数值
* @return 运算结果
*/
@Override
public int execute(int value) {
this.value = value; // 记录操作数值,用于后续撤销/重做
return adder.add(value);
}
/**
* 撤销命令:通过加负数抵消之前的加法操作
* @return 撤销后的结果
*/
@Override
public int undo() {
return adder.add(-value); // 原操作是+value,撤销则是-value
}
/**
* 重做命令:重新执行之前的加法操作
* @return 重做后的结果
*/
@Override
public int redo() {
return adder.add(value); // 再次执行+value操作
}
}
4. 调用者:CalculatorForm(计算器表单)
作为命令的触发者,持有命令对象,提供给客户端简单的操作接口(计算、撤销、重做),无需关心命令的具体实现。
/**
* 调用者类:计算器表单
* 提供用户操作接口,触发命令的执行、撤销和重做
*/
public class CalculatorForm {
private AbstractCommand command; // 持有命令对象
/**
* 设置命令对象(支持动态切换命令)
* @param command 具体命令实例
*/
public void setCommand(AbstractCommand command) {
this.command = command;
}
/**
* 触发计算操作(执行命令)
* @param value 待加数值
*/
public void compute(int value) {
int result = command.execute(value);
System.out.println("执行运算,运算结果为:" + result);
}
/**
* 触发撤销操作
*/
public void undo() {
int result = command.undo();
System.out.println("执行撤销,运算结果为:" + result);
}
/**
* 触发重做操作
*/
public void redo() {
int result = command.redo();
System.out.println("执行重做,运算结果为:" + result);
}
}
5. 客户端测试:Client
创建命令、调用者实例,绑定命令与调用者,模拟一系列加法、撤销、重做操作,验证命令模式的功能。
/**
* 客户端测试类:验证命令模式的撤销/重做功能
*/
public class Client {
public static void main(String[] args) {
// 1. 创建调用者(计算器表单)和具体命令
CalculatorForm calculator = new CalculatorForm();
AbstractCommand addCommand = new ConcreteCommand();
// 2. 绑定命令与调用者
calculator.setCommand(addCommand);
// 3. 模拟一系列操作
System.out.println("=== 开始执行加法运算 ===");
calculator.compute(10); // 执行:0 + 10 = 10
calculator.compute(5); // 执行:10 + 5 = 15
calculator.compute(10); // 执行:15 + 10 = 25
// 4. 测试撤销功能
System.out.println("\n=== 测试撤销操作 ===");
calculator.undo(); // 撤销:25 - 10 = 15
calculator.undo(); // 撤销:15 - 5 = 10
// 5. 测试重做功能
System.out.println("\n=== 测试重做操作 ===");
calculator.redo(); // 重做:10 + 5 = 15
calculator.redo(); // 重做:15 + 10 = 25
}
}
四、运行结果与验证
=== 开始执行加法运算 ===
执行运算,运算结果为:10
执行运算,运算结果为:15
执行运算,运算结果为:25
=== 测试撤销操作 ===
执行撤销,运算结果为:15
执行撤销,运算结果为:10
=== 测试重做操作 ===
执行重做,运算结果为:15
执行重做,运算结果为:25
结果分析:
-
执行逻辑:每次
compute调用都会触发命令的execute方法,累加数值并记录操作; -
撤销逻辑:
undo方法通过加负数抵消上一次操作,实现数值回溯; -
重做逻辑:
redo方法重新执行上一次记录的操作,恢复数值; -
解耦效果:客户端只需操作调用者(
CalculatorForm),无需直接与加法器(Adder)或命令细节交互。
五、命令模式的核心优势与适用场景
核心优势
-
解耦请求发送者与接收者:调用者(计算器)无需知道接收者(加法器)的实现,只需通过命令对象间接交互;
-
支持撤销/重做:命令对象可记录操作状态,通过反向执行实现撤销,重新执行实现重做;
-
支持命令队列与日志:可将命令对象存储在集合中,实现批量执行、任务调度或操作日志记录;
-
扩展性强:新增命令(如减法、乘法)时,只需新增具体命令类,无需修改现有调用者和接收者代码。
适用场景
-
需支持撤销/重做的场景:如文本编辑器、计算器、图形设计软件;
-
需记录操作日志的场景:如数据库事务、操作审计系统;
-
需批量执行命令的场景:如任务调度、命令批处理;
-
需解耦请求发送者与接收者的场景:如GUI按钮与业务逻辑、远程命令调用。
六、命令模式的扩展优化
本次实现仅支持单次撤销/重做,若需支持多次撤销/重做,可通过“命令历史栈”优化具体命令类:
public class ConcreteCommand extends AbstractCommand {
private Adder adder = new Adder();
private Stack<Integer> valueStack = new Stack<>(); // 存储所有操作数值
private Stack<Integer> redoStack = new Stack<>(); // 存储撤销的操作数值
@Override
public int execute(int value) {
valueStack.push(value);
redoStack.clear(); // 执行新命令后,清空重做栈
return adder.add(value);
}
@Override
public int undo() {
if (valueStack.isEmpty()) return adder.num;
int lastValue = valueStack.pop();
redoStack.push(lastValue);
return adder.add(-lastValue);
}
@Override
public int redo() {
if (redoStack.isEmpty()) return adder.num;
int redoValue = redoStack.pop();
valueStack.push(redoValue);
return adder.add(redoValue);
}
}
通过栈结构存储操作历史,可支持无限次撤销/重做,更贴合实际应用场景(如文本编辑器的多次回溯)。
七、实践总结
-
命令模式的核心是“请求封装为对象”,将操作的发起者与执行者解耦,同时支持对操作的管理(撤销、重做、日志);
-
本次实践中,
ConcreteCommand封装了加法操作,CalculatorForm作为调用者提供简单接口,Adder专注于核心运算,职责清晰; -
命令模式适合需要“操作回溯”或“解耦请求与执行”的场景,是实现撤销/重做功能的首选模式;
-
实际开发中,命令模式的典型应用包括:Spring的
JmsTemplate(消息命令封装)、GUI框架的按钮事件处理、数据库事务的提交/回滚。
通过本次加法计算器的实践,我深刻理解了命令模式如何通过封装请求实现灵活的操作管理。合理运用命令模式,能让系统的扩展性和可维护性大幅提升,尤其在需要复杂操作管理的场景中表现突出。

浙公网安备 33010602011771号