备忘录模式(Memento Pattern)
Posted 顧棟
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了备忘录模式(Memento Pattern)相关的知识,希望对你有一定的参考价值。
备忘录模式(Memento Pattern)
备忘录模式的定义
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
备忘录模式的优点
- 提供了一种可以恢复状态的机制。当用户需要时能够比较方便地将数据恢复到某个历史的状态。
- 实现了内部状态的封装。除了创建它的发起人之外,其他对象都不能够访问这些状态信息。
- 简化了发起人类。发起人不需要管理和保存其内部状态的各个备份,所有状态信息都保存在备忘录中,并由管理者进行管理,这符合单一职责原则。
备忘录模式的缺点
资源消耗大。如果要保存的内部状态信息过多或者特别频繁,将会占用比较大的内存资源。
备忘录模式的结构
备忘录模式的主要角色如下。
- 发起人(Originator)角色:记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,实现其他业务功能,它可以访问备忘录里的所有信息。
- 备忘录(Memento)角色:负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人。
- 管理者(Caretaker)角色:对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改。
备忘录模式的实现
一般实现
/**
* 发起人角色
*/
public class Originator {
/**
* 内部状态
*/
private String state = "";
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
/**
* 创建一个备忘录
*/
public Memento createMemento() {
return new Memento(this.state);
}
/**
* 恢复一个备忘录
*/
public void restoreMemento(Memento memento) {
this.setState(memento.getState());
}
}
/**
* 备忘录角色
*/
public class Memento {
/**
* 发起人的内部状态
*/
private String state = "";
/**
* 构造函数传递参数
*/
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
/**
* 备忘录管理员角色
*/
public class Caretaker {
/**
* 备忘录对象
*/
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
public static void main(String[] args) {
//定义出发起人
Originator originator = new Originator();
//定义出备忘录管理员
Caretaker caretaker = new Caretaker();
//创建一个备忘录
originator.setState("S0");
System.out.println("初始状态:" + originator.getState());
caretaker.setMemento(originator.createMemento());
originator.setState("S1");
System.out.println("新的状态:" + originator.getState());
//恢复一个备忘录
originator.restoreMemento(caretaker.getMemento());
System.out.println("恢复状态:" + originator.getState());
}
记录配置⽂文件版本信息场景
public class ConfigOriginator {
private ConfigFile configFile;
public ConfigFile getConfigFile() {
return configFile;
}
public void setConfigFile(ConfigFile configFile) {
this.configFile = configFile;
}
public ConfigMemento saveMemento() {
return new ConfigMemento(configFile);
}
public void getMemento(ConfigMemento memento) {
this.configFile = memento.getConfigFile();
}
}
/**
* 配置文件
*/
public class ConfigFile {
/**
* 版本号
*/
private String versionNo;
/**
* 内容
*/
private String content;
/**
* 时间
*/
private Date dateTime;
/**
* 操作人
*/
private String operator;
public ConfigFile(String versionNo, String content, Date dateTime, String operator) {
this.versionNo = versionNo;
this.content = content;
this.dateTime = dateTime;
this.operator = operator;
}
public String getVersionNo() {
return versionNo;
}
public void setVersionNo(String versionNo) {
this.versionNo = versionNo;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Date getDateTime() {
return dateTime;
}
public void setDateTime(Date dateTime) {
this.dateTime = dateTime;
}
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
}
/**
* 配置文件备忘录
*/
public class ConfigMemento {
private ConfigFile configFile;
public ConfigMemento(ConfigFile configFile) {
this.configFile = configFile;
}
public ConfigFile getConfigFile() {
return configFile;
}
public void setConfigFile(ConfigFile configFile) {
this.configFile = configFile;
}
}
public class Admin {
private int cursorIdx = 0;
private List<ConfigMemento> mementoList = new ArrayList<ConfigMemento>();
private Map<String, ConfigMemento> mementoMap = new ConcurrentHashMap<String, ConfigMemento>();
public void append(ConfigMemento memento) {
mementoList.add(memento);
mementoMap.put(memento.getConfigFile().getVersionNo(), memento);
cursorIdx++;
}
public ConfigMemento undo() {
if (--cursorIdx <= 0) return mementoList.get(0);
return mementoList.get(cursorIdx);
}
public ConfigMemento redo() {
if (++cursorIdx > mementoList.size()) return mementoList.get(mementoList.size() - 1);
return mementoList.get(cursorIdx);
}
public ConfigMemento get(String versionNo) {
return mementoMap.get(versionNo);
}
}
public class Client {
public static void main(String[] args) {
Admin admin = new Admin();
ConfigOriginator configOriginator = new ConfigOriginator();
configOriginator.setConfigFile(new ConfigFile("1000001", "配置内容A=版本1", new Date(), "小哥"));
admin.append(configOriginator.saveMemento());
configOriginator.setConfigFile(new ConfigFile("1000002", "配置内容A=版本2", new Date(), "小哥"));
admin.append(configOriginator.saveMemento());
configOriginator.setConfigFile(new ConfigFile("1000003", "配置内容A=版本3", new Date(), "小傅"));
admin.append(configOriginator.saveMemento());
configOriginator.setConfigFile(new ConfigFile("1000004", "配置内容A=版本4", new Date(), "小傅"));
admin.append(configOriginator.saveMemento());
// 历史配置(回滚)
configOriginator.getMemento(admin.undo());
System.out.println("历史配置(回滚)undo:" + JSON.toJSONString(configOriginator.getConfigFile()));
// 历史配置(回滚)
configOriginator.getMemento(admin.undo());
System.out.println("历史配置(回滚)undo:" + JSON.toJSONString(configOriginator.getConfigFile()));
// 历史配置(前进)
configOriginator.getMemento(admin.redo());
System.out.println("历史配置(前进)redo:" + JSON.toJSONString(configOriginator.getConfigFile()));
// 历史配置(获取)
configOriginator.getMemento(admin.get("1000002"));
System.out.println("历史配置(获取)get:" + JSON.toJSONString(configOriginator.getConfigFile()));
}
}
备忘录模式的适用场景
- 需要保存与恢复数据的场景,如玩游戏时的中间结果的存档功能。
- 需要提供一个可回滚操作的场景,如 Word、记事本、Photoshop,Eclipse 等软件在编辑时按 Ctrl+Z 组合键,还有数据库中事务操作。
- 需要监控的副本场景中。例如要监控一个对象的属性,但是监控又不应该作为系统的主业务来调用,它只是边缘应用,即使出现监控不准、错误报警也影响不大,因此一般的做法是备份一个主线程中的对象,然后由分析程序来分析。
注意事项
-
备忘录的生命期
备忘录创建出来就要在“最近”的代码中使用,要主动管理它的生命周期,建立就要使用,不使用就要立刻删除其引用,等待垃圾回收器对它的回收处理。
-
备忘录的性能
不要在频繁建立备份的场景中使用备忘录模式(比如一个for循环中),原因有二:一是控制不了备忘录建立的对象数量;二是大对象的建立是要消耗资源的,系统的性能需要考虑。因此,如果出现这样的代码,设计师就应该好好想想怎么修改架构了
本文主要参考:
- 小傅哥的《重学Java模式》
- 《C语言中文网》设计模式的相关内容
- 《设计模式之禅》第二版 秦小波
以上是关于备忘录模式(Memento Pattern)的主要内容,如果未能解决你的问题,请参考以下文章