0.参考资料
1.概述
- 在不破坏封装性的前提下, 捕获一个对象的内部状态 ,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。 —《设计模式》GoF
- 可以这里理解备忘录模式:现实生活中的备忘录是用来记录某些要去做的事情,或者是记录已经达成的共同意见的事情,以防忘记了。而在软件层面,备忘录模式有着相同的含义,备忘录对象主要用来记录一个对象的某种状态,或者某些数据,当要做回退时,可以从备忘录对象里获取原来的数据进行恢复操作.
1.1动机
- 在软件构建过程中,某些对象的状态在转换过程中,可能由于某种需要,要求程序能够回溯到对象的之前处于某个点时的状态。如果使用一些公有接口来让其他对象得到对象的状态,便会暴露对象的细节实现。
- 如何实现对象状态的良好保存与恢复?但同时又不会因此而破坏对象本身的封装性。
1.2结构
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630153049015-6673ffa8-c0b0-41c7-ac67-5440ac19e7c0.png#clientId=u9b952b2d-c941-4&from=paste&height=251&id=u9eb5686d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=502&originWidth=1464&originalType=binary&ratio=1&size=294242&status=done&style=none&taskId=u5f576fdb-425d-4c80-a960-6f79973f7a4&width=732)
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630156992788-5ae88485-a48f-4065-94b7-60ca96555d85.png#clientId=u9b952b2d-c941-4&from=paste&height=301&id=u0a7bd908&margin=%5Bobject%20Object%5D&name=image.png&originHeight=401&originWidth=762&originalType=binary&ratio=1&size=32886&status=done&style=none&taskId=ue819b357-2dba-40f2-987d-5edd80a240d&width=572)
- 备忘录模式的角色及职责
- Originator : 需要保存状态的对象, 即原发器: 可以生成自身状态的快照,也可以在需要时通过快照恢复自身状态的类
- Memento : 备忘录,负责保存好记录,即Originator内部状态
- Caretaker: 守护者对象,负责保存多个备忘录对象, 使用集合管理,提高效率
2.要点总结
宏观架构
1. 备忘录( Memento )存储**原发器( Originator )**对象的内部状态,在需要时恢复原发器的历史状态。
1. Memento模式的核心是**信息隐藏**,即Originator需要向外界隐藏信息,保持其封装性。但同时又需要将状态保持到外界( Memento )。
1. 由于现代语言运行时(如C#、Java等 )都具有相当的对象序列化支持,因此往往采用效率较高、又较容易正确实现的序列化方案来实现Memento模式。
微观代码
- **保存多个原发器originator对象的多个状态,**
- **思路: HashMap <String, 某集合>**
- **k-v: 原发器的唯一key, 对应的备忘录集合**
- 若原发器类的成员变量过多,会占用大量的资源,且每一次保存都会消耗一定内存. **为了节约内存,备忘录模式可以和原型模式配合使用.**
3.案例
需求
- 多人RPG游戏中, 角色有攻击力和防御力,**集体**刷Boss时:
- 在大战Boss前保存自身的状态(攻击力和防御力)
- 当大战Bosss时, 受到DeBuff, 攻击力和防御力下降
- 刷Boss后, 状态恢复到正常(战斗前状态)
传统方案
- ![image.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630156762171-a34ac287-179c-4024-b4b2-e823326f216b.png#clientId=u9b952b2d-c941-4&from=paste&height=210&id=uf30e5bb1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=420&originWidth=830&originalType=binary&ratio=1&size=31024&status=done&style=none&taskId=ub8e03fdd-8091-4d10-9637-c6720e8189d&width=415)
分析
1. 一个对象,就对应一个保存对象状态的对象, 这样当我们游戏的对象很多时,不利于管理,开销也很大.
1. 传统的方式是简单地做备份,new出另外一个对象出来,再把需要备份的数据放到这个新对象,但这就**暴露了对象内部的细节**
4.使用模式
方案
- 一个简易的备忘录模式( 单一原发器, 多备忘录)
类图
- ![Caretaker.png](https://cdn.nlark.com/yuque/0/2021/png/12524106/1630157559706-a52dfdbd-7749-4a7e-8f4a-03df889374f4.png#clientId=u9b952b2d-c941-4&from=ui&id=u0a7adfac&margin=%5Bobject%20Object%5D&name=Caretaker.png&originHeight=919&originWidth=868&originalType=binary&ratio=1&size=46805&status=done&style=none&taskId=u7a7df0cd-b45a-494c-bf05-adf317e0e4d)
代码
- 原发器 Originator
// 原发器
@Getter
@Setter
public class Originator {
private String version;
private String title;
/**
* 在该时刻保存当前状态
* @return 生成备忘录: 当前的状态
*/
public Memento createMemento(){
return new Memento(version, title);
}
/**
* 从指定 备忘录 回复状态
* @param memento 传入的备忘录: 需要的状态
*/
public void recoverStateFromMemento(Memento memento){
this.version = memento.getVersion();
this.title = memento.getTitle();
}
public void show(){
System.out.println("\t\t版本: " + version + ", 标题: " + title);
}
}
- 备忘录 Memento
// 对应的备忘录类
public class Memento {
private final String version;
private final String title;
public Memento(String version, String title) {
this.version = version;
this.title = title;
}
public String getVersion(){
return this.version;
}
public String getTitle(){
return this.title;
}
}
- 守护者 Caretaker
// 守护者. 当前模式: 保存一个原发器的多个备忘
public class Caretaker {
// (一个原发器)保存多个状态
private List<Memento> mementoList = new ArrayList<>();
// 其它方案
// 1. 一个原发器, 只保存一次状态
//Memento memento;
// 2. 多个原发器, 均可保存多个状态
//HashMap<String, Memento> mementoHashMap;
// 加入一个新状态. 可以设置一个返回值, 比如 int 来锁定当前Memento的存储下表
public void addMemento(Memento memento){
mementoList.add(memento);
}
// 获取一个状态. 简单化直接通过下标获取
public Memento getMemento(int index){
return mementoList.get(index);
}
}
- 测试
public class Client {
public static void main(String[] args) {
// 创建原发器对应的备忘录对应的的守护者
Caretaker caretaker = new Caretaker();
// 创建原发器并初始化
Originator originator = new Originator();
originator.setVersion("版本1.0");
originator.setTitle("刚刚完成");
System.out.println("--------------初始化展示: ");
originator.show();
// 创建当前状态备忘录, 并记录,
caretaker.addMemento(originator.createMemento());// 0
// 修改, 并记录
originator.setVersion("版本1.1");
originator.setTitle("修改了一次");
caretaker.addMemento(originator.createMemento());// 1
System.out.println("--------------第一次修改后展示: ");
originator.show();
// 修改2, 记录
originator.setVersion("版本final");
originator.setTitle("我觉得是最终版了");
caretaker.addMemento(originator.createMemento());// 2
System.out.println("--------------第二次修改后展示: ");
originator.show();
// 载入备忘录
System.out.println("--------------载入备忘录--------------");
System.out.println("--------------回滚到1.0");
originator.recoverStateFromMemento(caretaker.getMemento(0));
originator.show();
System.out.println("--------------回滚到1.1");
originator.recoverStateFromMemento(caretaker.getMemento(1));
originator.show();
System.out.println("--------------回滚到final");
originator.recoverStateFromMemento(caretaker.getMemento(2));
originator.show();
}
}
--------------初始化展示:
版本: 版本1.0, 标题: 刚刚完成
--------------第一次修改后展示:
版本: 版本1.1, 标题: 修改了一次
--------------第二次修改后展示:
版本: 版本final, 标题: 我觉得是最终版了
--------------载入备忘录--------------
--------------回滚到1.0
版本: 版本1.0, 标题: 刚刚完成
--------------回滚到1.1
版本: 版本1.1, 标题: 修改了一次
--------------回滚到final
版本: 版本final, 标题: 我觉得是最终版了
5.经典使用
Git版本控制
数据库的事务管理
游戏存档
文档编辑软件 中的 ctri + z
浏览器中的后退