备忘录模式Memento——给你的对象一剂“后悔药”
备忘录模式在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
一、备忘录模式
我们编辑word文档、写这篇博客的时候,经常用到 ctrl+z 这个快捷键,就是撤销当前操作回到上一步编辑状态。还有一些线上考试系统,在考到中间的时候退出,再重进还能在上一次的基础上接着做题。这里面都有一个环节,那就是回复到之前的某个节点,这个怎么实现的呢?
其中一种方式就是通过备忘录模式去实现,它能保存对象当前状态,并在之后恢复到此状态(后悔药),当然它也保证被保存的对象状态不能被外部访问,保证内部完整性,不向外透露。
角色
-
发起人: 发起人的内部要规定要备忘的范围,负责提供备案数据
-
备忘录: 存储发起人对象的内部状态,在需要的时候,可以向其他人提供这个内部状态,以方便负责人恢复发起人状态
-
负责人: 负责对备忘录进行管理(保存或提供备忘录)
二、代码实现
这里就以考试系统为例,中途退出再进入。
Originator 创建者:
/**
* Originator 创建者
* 其内部可以实现备忘录的创建以及恢复
* 负责创建一个备忘录,用来记录、恢复自身的内部状态。同时根据需要决定Memento存储自身的那些状态
*
* 考试系统(Originator)
*/
@ToString
public class ExaminationSystem {
// 题目序号
private int topicNum = 1;
// 剩余时间
private int remainingTime = 120;
// 答案缓存
private String answer = "";
// 开始答题
public void startTest(){
Console.log("开始答题:" + String.format("第%d题", topicNum));
remainingTime -=10;
Console.log("时间还剩" + remainingTime + "分钟");
answer = "答案1";
topicNum++;
Console.log("开始做"+String.format("第%d题", topicNum));
}
// 退出考试
public void quit(){
Console.log("---------------------");
Console.log("退出前的情况:"+this.toString());
Console.log("退出考试");
Console.log("---------------------");
}
// 创建备忘录
public Memento createMemoto(){
Memento memento = new Memento();
memento.topicNum = topicNum;
memento.remainingTime = remainingTime;
memento.answer = answer;
return memento;
}
// 获取备忘录,恢复当时的对象状态
public void restore(Memento memento){
this.topicNum = memento.topicNum;
this.remainingTime = memento.remainingTime;
this.answer = memento.answer;
System.out.println("恢复后的考试属性:"+this.toString());
}
}
Memento 备忘录类:
/**
* 备忘录类
*/
@ToString
public class Memento {
public int topicNum;
public int remainingTime;
public String answer;
}
/**
* 负责管理 Memento
*/
public class Caretaker {
//备忘录
Memento memento;
//存档
public void archive(Memento memoto){
this.memento = memoto;
}
//获取存档
public Memento getMemoto(){
return memento;
}
}
public class Client {
public static void main(String[] args) {
// 构建考试系统对象(Originator)
ExaminationSystem examination = new ExaminationSystem();
//开始考试
examination.startTest();
//构建caretaker,用于试卷存档
Caretaker caretaker = new Caretaker();
//通过考试系统本身创建备忘录,caretaker执行存档操作
caretaker.archive(examination.createMemoto());
//退出考试
examination.quit();
//重新开启考试,并通过caretaker恢复考试进度
ExaminationSystem examination_new = new ExaminationSystem();
examination_new.restore(caretaker.getMemoto());
}
}
开始答题:第1题
时间还剩110分钟
开始做第2题
---------------------
退出前的情况:ExaminationSystem(topicNum=2, remainingTime=110, answer=答案1)
退出考试
---------------------
恢复后的考试属性:ExaminationSystem(topicNum=2, remainingTime=110, answer=答案1)
三、适用场景
上面的介绍可以看到备忘录模式可以保存一份对象的快照,一般来说,一个对象有一个对应的备忘对象,记录对象中要备忘的字段,而多个对象的备忘,同一由同一个负责人进行管理,可以用 Map 来做到这一点,负责人中的备忘容器是一个 Map 类型的数据,值是一个实现备忘接口的数据结构即可。
有了这个快照那就等于有了“后悔药”,比如可以暂时将游戏存档,可以撤销操作回到之前的状态,再比如把保存的备忘录序列化成字符串保存在磁盘中,下次启动的时候从磁盘中获取状态,这样就可以做一个简单的快照了,等等。
另外要注意一下备忘模式的优缺点:
-
优点:
- 备忘录模式仅做数据备忘,不论该数据是否正确。
- 设计模式最大的优点就是解耦,各司其职,发起人只需要提供备忘数据,不需要对其进行管理
-
缺点
- 实际应用中,备忘录模式大多是多状态的,如果进行大量备忘的话,会占用大量内存,当然,如果持久化在磁盘中的话,会减少内存占用,但会增加IO操作,这就需要开发者根据实际业务情况进行取舍了。
使用场景
-
需要保存一个对象在某一个时刻的状态或者部分状态
-
如果用一个接口来让其他对象的到这些状态,将会暴露这个对象的实现细节并破坏对象的的封装性,一个对象不希望外界直接访问其内部状态,通过中间对象可以间接访问其内部状态