Java撤销功能实现咨询:如何存储变更前后值并支持撤销
实现Java变更操作的撤销功能思路
嘿,刚接触Java就考虑到做撤销功能,这个意识真的不错!我给你分享几个实用的实现思路,尤其是针对你提到的setName这类方法的场景~
核心思路:命令模式(Command Pattern)
这是实现撤销/重做功能最经典的方案,核心是把每一次变更操作封装成一个独立的命令对象,每个命令里保存执行和撤销需要的所有信息,然后用栈来记录操作历史——毕竟撤销是“倒序”回滚,栈的“后进先出”特性刚好匹配这个需求。
具体步骤(以setName为例)
- 定义命令接口:统一所有变更命令的执行和撤销方法
- 实现具体命令类:针对
setName编写专门的命令,保存变更前后的名称和目标产品对象 - 维护操作历史栈:用栈存储执行过的命令,撤销时从栈顶取出命令执行撤销逻辑
代码示例
1. 基础产品类
public class Product { private String name; public String getName() { return name; } public void setName(String name) { // 这里可以保留你原来的null/空字符串处理逻辑 this.name = name; } }
2. 命令接口
public interface Command { // 执行当前变更操作 void execute(); // 撤销当前变更操作 void undo(); }
3. 针对setName的具体命令
public class SetNameCommand implements Command { private Product targetProduct; private String oldName; // 变更前的旧名称 private String newName; // 要设置的新名称 public SetNameCommand(Product product, String newName) { this.targetProduct = product; // 初始化时就保存当前的旧值,避免后续被覆盖 this.oldName = product.getName(); this.newName = newName; } @Override public void execute() { // 执行实际的名称修改,这里可以加入你原本的null/空字符串处理逻辑 targetProduct.setName(newName); // 比如如果需要把null转为空字符串: // targetProduct.setName(newName == null ? "" : newName); } @Override public void undo() { // 撤销就是把名称恢复为变更前的旧值 targetProduct.setName(oldName); } }
4. 撤销管理器(处理操作历史)
import java.util.Stack; public class UndoManager { // 用栈存储执行过的命令,栈顶是最近一次操作 private Stack<Command> commandHistory = new Stack<>(); // 执行命令并记录到历史 public void executeCommand(Command command) { command.execute(); commandHistory.push(command); } // 执行撤销操作 public void undoLastOperation() { if (!commandHistory.isEmpty()) { Command lastCommand = commandHistory.pop(); lastCommand.undo(); } else { System.out.println("没有可撤销的操作啦!"); } } }
5. 测试使用
public class TestUndo { public static void main(String[] args) { Product myProduct = new Product(); myProduct.setName("初始产品名称"); UndoManager undoManager = new UndoManager(); // 第一次修改名称 undoManager.executeCommand(new SetNameCommand(myProduct, "新品名1")); System.out.println("当前名称:" + myProduct.getName()); // 输出:当前名称:新品名1 // 第二次修改为null undoManager.executeCommand(new SetNameCommand(myProduct, null)); System.out.println("当前名称:" + myProduct.getName()); // 输出:当前名称:null // 第一次撤销 undoManager.undoLastOperation(); System.out.println("撤销后名称:" + myProduct.getName()); // 输出:撤销后名称:新品名1 // 第二次撤销 undoManager.undoLastOperation(); System.out.println("再次撤销后名称:" + myProduct.getName()); // 输出:再次撤销后名称:初始产品名称 // 没有可撤销的操作了 undoManager.undoLastOperation(); // 输出:没有可撤销的操作啦! } }
可选思路:备忘录模式(Memento Pattern)
如果你的产品类有很多属性,或者需要一次性撤销所有属性的变更,可以用备忘录模式——它的核心是保存对象的快照,撤销时直接恢复到快照状态。不过这种方式会占用更多内存,适合属性不多或者需要整体回滚的场景。
简单示例(针对Product)
// 备忘录类,保存Product的状态 public class ProductMemento { private String name; public ProductMemento(String name) { this.name = name; } public String getName() { return name; } } // 修改Product类,支持生成和恢复备忘录 public class Product { private String name; // 生成当前状态的备忘录 public ProductMemento saveToMemento() { return new ProductMemento(this.name); } // 从备忘录恢复状态 public void restoreFromMemento(ProductMemento memento) { this.name = memento.getName(); } // 原有的get/set方法 public String getName() { return name; } public void setName(String name) { this.name = name; } }
然后同样用栈存储备忘录,撤销时取出最近的快照恢复即可。
注意事项
- 如果需要重做功能,可以再维护一个重做栈:撤销时把命令从历史栈移到重做栈,重做时再移回历史栈并执行
execute。 - 多线程环境下,栈的操作需要加锁保证线程安全。
- 命令模式更适合单个属性的细粒度变更,备忘录模式适合对象整体状态的回滚,根据你的需求选择就行~
内容的提问来源于stack exchange,提问作者Teardrop
相关产品推荐
相关产品推荐

