定义:
在不破坏封装性的前提下,捕获对象的内部状态并将其在对象外部存储起来,这样就可以在之后需要的时候将对象恢复到之前保存的状态。
备忘录模式又叫快照模式,其实就是一种数据备份的方法机制。
设计类图:
备忘录模式中的三个主要角色:
- Originator发起人角色:内部状态会发生变化的角色,负责内部创建备份数据和恢复数据状态。
- Memento备忘录角色: 负责存储发起人的内部状态。
- Caretaker备忘录管理者角色:对备忘录对象进行保存和提取。
示例代码:
public class Originator {
private String state;
/**
* 创建一个当前状态的备忘录对象
*/
public Memento createMemento(){
return new Memento(state);
}
/**
* 恢复状态
*/
public void restoreMemento(Memento memento){
this.state = memento.getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
System.out.println("当前状态:" + this.state);
}
}
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 this.memento;
}
/**
* 保存备忘录
*/
public void saveMemento(Memento memento){
this.memento = memento;
}
}
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
//改变发起人对象的状态
originator.setState("stateA");
//创建备忘录对象,并将发起人对象的状态储存起来
caretaker.saveMemento(originator.createMemento());
//修改发起人的状态
originator.setState("stateB");
//恢复发起人对象的状态
originator.restoreMemento(caretaker.getMemento());
System.out.println("恢复状态:"+originator.getState());
}
}
输出结果:
多状态的备忘录模式
上面的示例代码展示的情况比较简单,Originator中只有一个state成员变量需要保存,但实际中可能不止一个,会有很多个状态变量需要处理,那么在备份的时候就需要将所有的变量都存储起来,一种方法就是将变量名和对应的值都保存到一个map当中。当然还有比较简单的方法就是利用原型模式,我们可以将当前对象clone一个一模一样的对象来保存备份,通过clone的方式甚至都不需要备忘录管理者角色。下面是示例代码:
public class Originator implements Cloneable {
private Originator backup;
private String state;
private String state2;
private String state3;
/**
* 创建备份
*/
public void createMemento() {
try {
this.backup = this.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
}
/**
* 恢复备份
*/
public void restoreMemento(){
this.setState(this.backup.getState());
this.setState2(this.backup.getState2());
this.setState3(this.backup.getState3());
}
@Override
protected Originator clone() throws CloneNotSupportedException {
return (Originator) super.clone();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getState2() {
return state2;
}
public void setState2(String state2) {
this.state2 = state2;
}
public String getState3() {
return state3;
}
public void setState3(String state3) {
this.state3 = state3;
}
}
当然,我们知道通过clone的方式是有缺陷的,对象的复制都是浅拷贝,这样对于那些是对象类型的成员变量还是跟原来共享的不能完全的隔离。所以最好的方式应该是使用序列化(Serializable)来进行存储。
更安全的备忘录模式
作为备份的数据,是用作将来恢复系统使用的,所以必须保证数据的完整性、纯净性,不能随意被别人修改,否则就失去了作为备份的意义。那么我们就必须想办法不让别人能够去修改它,怎么做呢,这里可以备忘录通过实现一个标识接口(空接口)的办法来对外部进行隔离。示例代码:
public class Originator {
private String state;
/**
* 创建备份
*/
public IMemento createMemento(){
return new Memento(state);
}
/**
* 恢复备份
*/
public void restoreMemento(IMemento memento){
this.state = ((Memento) memento).getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
System.out.println("当前状态:" + this.state);
}
private class Memento implements IMemento {
private String state;
public Memento(String state){
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
}
public interface IMemento {
}
public class Caretaker {
private IMemento memento;
/**
* 获取备忘录
*/
public IMemento getMemento(){
return this.memento;
}
/**
* 保存备忘录
*/
public void saveMemento(IMemento memento){
this.memento = memento;
}
}
可以看到我们的备忘录角色Memento现在是作为Originator的一个内部私有类,并且实现了IMemento接口,而IMemento接口是一个空接口,它什么方法也没有,Originator在创建和恢复备份的时候都是引用的IMemento接口类型,而在备忘录管理者Caretaker对备忘录的存取也都是通过IMemento接口类型,这样客户端在调用过程中能接触到的只有IMemento接口,而它不具有任何方法,所以就避免了别人去调用它的方法进行操作。
多重备份的备忘录模式
前面介绍的都是只有一份备份数据的情况,然而在实际当中很多情况下都是要做多重数据备份的,比如我今天发现系统异常,想要将系统数据恢复到昨天甚至前天或上个星期五的状态,这个时候我们就需要有多个备份数据提供选择。可以选择恢复多重备份的点叫做检查点(check point), 检查点就是一个标记,例如它可以是一个系统的时间戳等,每个检查点会对应一个备份数据以备后期选择性恢复。示例代码:
public class Caretaker {
private HashMap<String, Memento> mementoMap = new HashMap<>();
/**
* 获取备忘录
*/
public Memento getMemento(String time){
return this.mementoMap.get(time);
}
/**
* 保存备忘录
*/
public void saveMemento(String time, Memento memento){
this.mementoMap.put(time, memento);
}
}
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
//多重时间备份
caretaker.saveMemento("2018-06-21", originator.createMemento());
caretaker.saveMemento("2018-06-22", originator.createMemento());
caretaker.saveMemento("2018-06-23", originator.createMemento());
caretaker.saveMemento("2018-06-24", originator.createMemento());
//恢复到指定时间点的备份
originator.restoreMemento(caretaker.getMemento("2018-06-22"));
}
}
这里示例代码就是通过日期时间作为检查点,在Caretaker当中我们通过一个HashMap来存放每天对应的备份数据,需要恢复数据的时候可以选择恢复到指定日期的备份状态。
使用场景
备忘录模式一般用在需要恢复数据或者需要现场保护的场景下,比如数据库操作的事务管理,一旦出现错误可以回滚到原始状态。备忘录当中的备份数量如果太多或者单个备份对象的内存消耗比较大的话,也会带来性能问题,由于是保存在内存当中,所以也会占用大量的系统资源。
参考: