原文来自battcn
定义
备忘录模式(Memento Pattern)属于行为型模式的一种,在不破坏封装特性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样就可以将该对象恢复到原先保存的状态。
UML类图
角色组成
- Memento(备忘录角色): 负责存储原发器对象的内部状态,但是具体需要存储哪些数据是由原发器对象来决定的,在需要的时候提供原发器需要的内部状态。PS:这里可以存储状态。
- Originator(发起人(原发器)角色): 记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。
- Caretaker(备忘录负责人(管理者)角色): 对备忘录对象进行管理,但是不能对备忘录对象的内容进行操作或检查。
举个例子
- 创建一个备忘录角色备忘录,内部定义了一个变量用来区分当前对象状态
public class Memento {
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return this.state;
}
public void setState(String state) {
this.state = state;
}
}
- 接着创建一个原发器对象
Originator
,定义了创建备忘录对象和回滚的对象
public class Originator {
private String state;
public String getState() {
return this.state;
}
public void setState(String state) {
this.state = state;
}
/**
* 创建对象
*
* @return 备忘录对象
*/
public Memento createMemento() {
return new Memento(state);
}
/**
* 从备忘录中恢复
*
* @param memento 恢复的对象
*/
public void restoreMemento(Memento memento) {
this.state = memento.getState();
}
}
- 创建备忘录管理者
Caretaker
,顾名思义就是来管理备忘录对象的
public class Caretaker {
/**
* 备忘录对象
*/
private Memento memento;
/**
* 获取备忘录
*
* @return 备忘录对象
*/
public Memento retrieveMemento() {
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("状态A");
System.out.println("当前状态:" + originator.getState());
// 存储内部状态
caretaker.saveMemento(originator.createMemento());
System.out.println("存档");
// 改变状态
originator.setState("状态B");
System.out.println("当前状态:" + originator.getState());
// 改变状态
originator.setState("状态C");
System.out.println("当前状态:" + originator.getState());
// 恢复状态
originator.restoreMemento(caretaker.retrieveMemento());
System.out.println("读档");
System.out.println("恢复后状态:" + originator.getState());
}
}
- 运行结果
当前状态:状态A
存档
当前状态:状态B
当前状态:状态C
读档
恢复后状态:状态A
多种实现
备忘录模式按照备忘录角色的形态不同,分为白箱实现与黑箱实现,两种模式与备忘录角色提供的接口模式有关;
窄接口:负责人(Caretaker)对象(和其他除发起人对象之外的任何对象)看到的是备忘录的窄接口(narrow interface),这个窄接口只允许它把备忘录对象传给其他的对象。
宽接口:与负责人对象看到的窄接口相反的是,发起人对象可以看到一个宽接口(wide interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。
上一个实现,是采用白箱实现的,接下来我们看看黑箱实现。
黑箱实现
- MementoIF(备忘录角色): 空接口,不作任何实现。
- Originator(发起人(原发器)角色): 记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。这里Memento做为原发器的私有内部类,来存储备忘录。备忘录只能由原发器对象来访问它内部的数据,原发器外部的对象不应该能访问到备忘录对象的内部数据。
- Caretaker(备忘录负责人(管理者)角色): 对备忘录对象进行管理,但是不能对备忘录对象的内容进行操作或检查。
实现代码
备忘录窄接口
public interface MementoIF {
}
接着创建一个原发器对象Originator
,其中定义了一个私有化的内部类Memento
实现了MementoIF
接口,只有当前对象能访问。
public class Originator {
private String state;
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
/**
* 创建一个新的备忘录对象
*/
public MementoIF createMemento() {
return new Memento(state);
}
/**
* 发起人恢复到备忘录对象记录的状态
*/
public void restoreMemento(MementoIF memento) {
this.setState(((Memento) memento).getState());
}
//静态内部类Memento
private class Memento implements MementoIF {
private String state;
private Memento(String state) {
this.state = state;
}
private String getState() {
return state;
}
private void setState(String state) {
this.state = state;
}
}
}
创建备忘录管理者Caretaker
,管理备忘录对象的
public class Caretaker {
/**
* 备忘录对象
*/
private MementoIF memento;
/**
* 获取备忘录对象
*/
public MementoIF retrieveMemento() {
return memento;
}
/**
* 保存备忘录对象
*/
public void saveMemento(MementoIF memento) {
this.memento = memento;
}
}
创建测试类
public class Client {
public static void main(String[] args) {
Originator originator = new Originator();
Caretaker caretaker = new Caretaker();
originator.setState("状态A");
System.out.println("当前状态:" + originator.getState());
// 改变状态
originator.setState("状态B");
System.out.println("当前状态:" + originator.getState());
// 存储内部状态
caretaker.saveMemento(originator.createMemento());
System.out.println("存档");
// 改变状态
originator.setState("状态C");
System.out.println("当前状态:" + originator.getState());
// 恢复状态
originator.restoreMemento(caretaker.retrieveMemento());
System.out.println("读档");
System.out.println("恢复后状态:" + originator.getState());
}
}
运行结果
当前状态:状态A
当前状态:状态B
存档
当前状态:状态C
读档
恢复后状态:状态B
两者的代码结构是比较类似的,本质区别就是外部能不能访问备忘录的状态,备忘录角色具有安全等级;这里关于备忘录角色 -> 白箱实现利用的宽接口,黑箱模式利用的窄接口;
多重检查点
前面所给出的白箱和黑箱的示意性实现都是只存储一个状态的简单实现,也可以叫做只有一个检查点。常见的系统往往需要存储不止一个状态,而是需要存储多个状态,或者叫做有多个检查点。 这种情况只需要使用有序队列方式可以很容易达到多重检查点。
备忘录模式可以将发起人对象的状态存储到备忘录对象里面,备忘录模式可以将发起人对象恢复到备忘录对象所存储的某一个检查点上。
总结
优点
- 给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态。
- 实现了信息的封装,使得用户不需要关心状态的保存细节。
缺点
- 消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
- 由于备份的信息是由发起人自己提供的,所以管理者无法预知备份的信息的大小,所以在客户端使用时,可能一个操作占用了很大的内存,但客户端并不知晓。
适用场景
- 需要保存/恢复数据的相关状态场景。
- 提供一个可回滚的操作。