备忘录模式(Memento)
备忘录模式主要实现的时在一个运行的对象时, 能够在不破坏封装性的前提下备份, 将备份的数据保存到对象外的另一个对象中, 使得运行对象随时可以恢复到备份时的状态.
关键要注意的一点是不破坏类的封装性, 备份对象在外部无法改变
如下是一个经典应用示例
由于要做json序列化存储, 所以前提要引入 Jackson 依赖包
xml
<!-- 引入 Jackson 依赖 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.17.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.17.0</version>
</dependency>
java
// 备份类, 保存主类所有属性
final class StrategyMemento {
// 由于进行了json序列化存储, 所以, 主类的所有状态都在state中
private final String state;
// 备份类中额外存储备份时间
private final LocalDateTime saveTime;
StrategyMemento(String state) {
this.state = state;
this.saveTime = LocalDateTime.now();
}
// 用于恢复备份的接口
public String getState() { return state; }
public LocalDateTime getSaveTime() { return saveTime; }
}
// 备份版本管理类, 将所有的备份版本使用栈结构存储, 最后的备份最先弹出
class MementoManager {
private final Stack<StrategyMemento> history = new Stack<>();
public void push(StrategyMemento memento) {
history.push(memento);
}
public StrategyMemento pop() {
if (history.isEmpty()) {
System.out.println("no more memento to pop.");
return null;
}
return history.pop();
}
}
class Originator {
// 用来存储未序列化的属性列表
private static final ObjectMapper mapper = new ObjectMapper();
private String param_1;
private int param_2;
private Map<String, Object> param_3;
private List<String> param_4;
// 所有属性必须都有getter方法才能完成序列化
public String getParam_1() { return param_1; }
public int getParam_2() { return param_2; }
public Map<String, Object> getParam_3() { return param_3; }
public List<String> getParam_4() { return param_4; }
public void modify(String a, int b, Map<String, Object> c, List<String> d) {
this.param_1 = a;
this.param_2 = b;
this.param_3 = c;
this.param_4 = d;
}
// 执行备份, 序列化存储, 将所有属性保存到StrategyMemento类对象中
public StrategyMemento save() {
try {
String stateJson = mapper.writeValueAsString(this);
return new StrategyMemento(stateJson);
} catch (JsonProcessingException e) {
throw new RuntimeException("备份失败:" + e.getMessage());
}
}
// 恢复备份
public void restore(StrategyMemento backup) {
try {
Originator obj = mapper.readValue(backup.getState(), Originator.class);
this.param_1 = obj.param_1;
this.param_2 = obj.param_2;
this.param_3 = obj.param_3;
this.param_4 = obj.param_4;
System.out.println(">>> 已恢复到" + backup.getSaveTime().toString() + "版本");
} catch (IOException e) {
throw new RuntimeException("恢复失败: " + e.getMessage());
}
}
}
总体思想就是
- 定义一个备份类, 用来存储每一个时刻的属性备份
- 在主类中增加save和restore这样的方法, 用来保存和恢复备份, save时生成备份对象, restore时从备份对象中恢复
- 定义一个Manager类用来存储所有的备份, 可以用栈的方式来存储, 也可以用List或者Map, 根据业务需求来选择适当的容器
通过上述步骤, 就实现了一个备忘录模式.
备忘录模式要注意, 备份类生成的对象, 是不可变更的, 它一旦生成, 就只能通过restore方法去恢复状态, 这是重点
另外一点是, 可以采用各种方式备份, 但是一定要注意是深拷贝的备份, 不能是浅拷贝
下面调用上述代码进行测试:
java
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.*;
import java.time.LocalDateTime;
public class MementoPattern {
public static void main(String[] args) {
// 第一次数据修改
Originator originObj = new Originator();
Map<String, Object> map = new HashMap<>();
map.put("obj-1", 100);
map.put("obj-2", 200);
List<String> list = new ArrayList<>();
list.add("string 1");
list.add("string 2");
originObj.modify("origin-1", 3, map, list);
// 执行备份
MementoManager mementoManager = new MementoManager();
mementoManager.push(originObj.save());
// 第二次数据修改
map.put("obj-3", 300);
list.add("string 3");
originObj.modify("origin-2", 4, map, list);
mementoManager.push(originObj.save());
// 第三次修改数据
map.put("obj-2", 220);
map.remove("obj-3");
list.remove(1);
list.add("string 4");
originObj.modify("origin-3", 5, map, list);
mementoManager.push(originObj.save());
// 导出变更历史
StrategyMemento backup_3 = mementoManager.pop();
StrategyMemento backup_2 = mementoManager.pop();
StrategyMemento backup_1 = mementoManager.pop();
System.out.println("最新版本:");
System.out.print("param_1:" + originObj.getParam_1() + " | ");
System.out.print("param_2:" + originObj.getParam_2() + " | ");
System.out.print("param_3:" + originObj.getParam_3() + " | ");
System.out.println("param_4:" + originObj.getParam_4());
System.out.println("===================第一次修改 ===================");
originObj.restore(backup_1);
System.out.print("param_1:" + originObj.getParam_1() + " | ");
System.out.print("param_2:" + originObj.getParam_2() + " | ");
System.out.print("param_3:" + originObj.getParam_3() + " | ");
System.out.println("param_4:" + originObj.getParam_4());
System.out.println("===================第二次修改 ===================");
originObj.restore(backup_2);
System.out.print("param_1:" + originObj.getParam_1() + " | ");
System.out.print("param_2:" + originObj.getParam_2() + " | ");
System.out.print("param_3:" + originObj.getParam_3() + " | ");
System.out.println("param_4:" + originObj.getParam_4());
System.out.println("===================第三次修改 ===================");
originObj.restore(backup_3);
System.out.print("param_1:" + originObj.getParam_1() + " | ");
System.out.print("param_2:" + originObj.getParam_2() + " | ");
System.out.print("param_3:" + originObj.getParam_3() + " | ");
System.out.println("param_4:" + originObj.getParam_4());
}
}
运行结果:
param_1:origin-3 | param_2:5 | param_3:{obj-1=100, obj-2=220} | param_4:[string 1, string 3, string 4]
===================第一次修改 ===================
>>> 已恢复到2026-01-03T16:14:11.223741700版本
param_1:origin-1 | param_2:3 | param_3:{obj-1=100, obj-2=200} | param_4:[string 1, string 2]
===================第二次修改 ===================
>>> 已恢复到2026-01-03T16:14:11.224278800版本
param_1:origin-2 | param_2:4 | param_3:{obj-1=100, obj-2=200, obj-3=300} | param_4:[string 1, string 2, string 3]
===================第三次修改 ===================
>>> 已恢复到2026-01-03T16:14:11.224891400版本
param_1:origin-3 | param_2:5 | param_3:{obj-1=100, obj-2=220} | param_4:[string 1, string 3, string 4]
通过输出可以看到, 每一次的恢复都是完美的