1 定义:
1.1 定义:Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later. (在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。)
1.2 通用类图:
个人认为更易理解的类图
Originator发起人角色
记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。
Memento备忘录角色
负责存储Originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。
Caretaker备忘录管理员角色
对备忘录进行管理、保存和提供备忘录。
1.3 通用代码:
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 class Client {
public static void main(String[] args) {
// 定义出发起人
Originator originator = new Originator();
// 定义出备忘录管理员
Caretaker caretaker = new Caretaker();
// 创建一个备忘录
caretaker.setMemento(originator.createMemento());
// 恢复一个备忘录
originator.restoreMemento(caretaker.getMemento());
}
}
2 优点
暂无
3 缺点
暂无
4 应用场景
4.1 需要保有存和恢复数据的相关状态场景;
4.2 提供一个可回滚的操作;
4.3 需要监控的副本场景中;(如要监控一个对象的属性,但监控又不应该作为系统的主业务来调用,它只是边缘应用,即使出现监控不准、错误报警也影响不大。)
4.4 数据库中的事务处理就使用备忘录模式。
5 注意事项
5.1 备忘录的生命周期:创建就要使用,要主动管理它的生命。
5.2 备忘录的性能:不要在频繁建立备份的场景中使用备忘录模式(比如一个for循环中),原因是:一是控制不了其数量,二是大对象的建立是要消耗资源的,系统的性能需要考虑。
6 扩展
6.1 clone方式的备忘录:通过复制的方式产生一个对象的内部状态,类图如下:
从类图上来看,发起人角色融合了发起人角色和备忘录角色,代码如下:
public class Originator implements Cloneable {
// 内部状态
private String state = "";
// 此二方法原为Memento所有。
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
// 创建一个备忘录
public Originator createMemento() {
return this.clone();
}
// 恢复一个备忘录
public void restoreMemento(Originator _originator) {
this.setState(_originator.getState());
}
// 克隆当前对象
@Override
protected Originator clone() {
try {
return (Originator) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
public class Caretaker {
// 发起人对象
private Originator originator;
public Originator getOriginator() {
return originator;
}
public void setOriginator(Originator originator) {
this.originator = originator;
}
}
public class Client {
public static void main(String args[]) {
Originator o = new Originator();
Caretaker c = new Caretaker();
o.setState("version 1.0.0");
c.setOriginator(o.clone());
System.out.println("当前状态:" + o.getState());
o.setState("version 2.0.0");
System.out.println("修改后状态:" + o.getState());
o.restoreMemento(c.getOriginator());
System.out.println("恢复后状态:" + o.getState());
}
}
测试:
当前状态:version 1.0.0
修改后状态:version 2.0.0
恢复后状态:version 1.0.0
仍可以进一步精简,去掉管理备忘录角色
public class Originator implements Cloneable {
private Originator backup;
// 内部状态
private String state = "";
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
// 创建一个备忘录
public void createMemento() {
this.backup = this.clone();
}
// 恢复一个备忘录
public void restoreMemento() {
// 在进行恢复前应该进行断言,防止空指针
this.setState(this.backup.getState());
}
// 克隆当前对象
@Override
protected Originator clone() {
try {
return (Originator) super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return null;
}
}
public class Client {
public static void main(String[] args) {
// 定义发起人
Originator originator = new Originator();
// 建立初始状态
originator.setState("初始状态...");
System.out.println("初始状态是:" + originator.getState());
// 建立备份
originator.createMemento();
// 修改状态
originator.setState("修改后的状态...");
System.out.println("修改后状态是:" + originator.getState());
// 恢复原有状态
originator.restoreMemento();
System.out.println("恢复后状态是:" + originator.getState());
}
}
对比以上两例,可以发现:程序精简了很多,而且高层模块的依赖也减少了。现在再考虑一下原型模式 深拷贝与浅拷贝的问题,在复杂的场景下它会让你的程序逻辑异常混乱,出现错误也很难跟踪。因此 Clone 方式的备忘录模式适用于较简单的场景 。
6.2 多状态的备忘录模式:实际开发中,一个对象可能有多个状态,一个JavaBean有多个属性非常常见,如果随上所述,就要写一堆的状态备份、还原语句?这里介绍一个工具类BeanUtils,可以把类的所有属性值转换到HashMap中,亦可以把HashMap中的值放入对象中。
此方案的类图如下:
源代码如下:
public class Originator {
// 内部状态
private String state1 = "";
private String state2 = "";
private String state3 = "";
public String getState1() {
return state1;
}
public void setState1(String state1) {
this.state1 = state1;
}
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;
}
// 创建一个备忘录
public Memento createMemento() {
return new Memento(BeanUtils.backupProp(this));
}
// 恢复一个备忘录
public void restoreMemento(Memento _memento) {
BeanUtils.restoreProp(this, _memento.getStateMap());
}
// 增加一个toString方法
@Override
public String toString() {
return "state1=" + state1 + "\nstat2=" + state2 + "\nstate3=" + state3;
}
}
public class Memento {
// 接受HashMap作为状态
private HashMap<String, Object> stateMap;
// 接受一个对象,建立一个备份
public Memento(HashMap<String, Object> map) {
this.stateMap = map;
}
public HashMap<String, Object> getStateMap() {
return stateMap;
}
public void setStateMap(HashMap<String, Object> stateMap) {
this.stateMap = stateMap;
}
}
public class Caretaker {
// 备忘录对象
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
public class BeanUtils {
// 把bean的所有属性及数值放入到Hashmap中
public static HashMap<String, Object> backupProp(Object bean) {
HashMap<String, Object> result = new HashMap<String, Object>();
try {
// 获得Bean描述
BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
// 获得属性描述
PropertyDescriptor[] descriptors = beanInfo
.getPropertyDescriptors();
// 遍历所有属性
for (PropertyDescriptor des : descriptors) {
// 属性名称
String fieldName = des.getName();
// 读取属性的方法
Method getter = des.getReadMethod();
// 读取属性值
Object fieldValue = getter.invoke(bean, new Object[] {});
if (!fieldName.equalsIgnoreCase("class")) {
result.put(fieldName, fieldValue);
}
}
} catch (Exception e) {
// 异常处理
}
return result;
}
// 把HashMap的值返回到bean中
public static void restoreProp(Object bean, HashMap<String, Object> propMap) {
try {
// 获得Bean描述
BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass());
// 获得属性描述
PropertyDescriptor[] descriptors = beanInfo
.getPropertyDescriptors();
// 遍历所有属性
for (PropertyDescriptor des : descriptors) {
// 属性名称
String fieldName = des.getName();
// 如果有这个属性
if (propMap.containsKey(fieldName)) {
// 写属性的方法
Method setter = des.getWriteMethod();
setter.invoke(bean, new Object[] { propMap.get(fieldName) });
}
}
} catch (Exception e) {
// 异常处理
System.out.println("shit");
e.printStackTrace();
}
}
}
public class Client {
public static void main(String[] args) {
// 定义出发起人
Originator ori = new Originator();
// 定义出备忘录管理员
Caretaker caretaker = new Caretaker();
// 初始化
ori.setState1("中国");
ori.setState2("强盛");
ori.setState3("繁荣");
System.out.println("===初始化状态===\n" + ori);
// 创建一个备忘录
caretaker.setMemento(ori.createMemento());
// 修改状态值
ori.setState1("软件");
ori.setState2("架构");
ori.setState3("优秀");
System.out.println("\n===修改后状态===\n" + ori);
// 恢复一个备忘录
ori.restoreMemento(caretaker.getMemento());
System.out.println("\n===恢复后状态===\n" + ori);
}
}
通过这种方式,无论有多少状态都可以。
6.3 多备份的备忘录:可在通用源码上添加如下即可:
public class Caretaker {
// 容纳备忘录的容器
private HashMap<String, Memento> memMap = new HashMap<String, Memento>();
public Memento getMemento(String idx) {
return memMap.get(idx);
}
public void setMemento(String idx, Memento memento) {
this.memMap.put(idx, memento);
}
}
public class Client {
public static void main(String[] args) {
// 定义出发起人
Originator originator = new Originator();
// 定义出备忘录管理员
Caretaker caretaker = new Caretaker();
// 创建两个备忘录
caretaker.setMemento("001", originator.createMemento());
caretaker.setMemento("002", originator.createMemento());
// 恢复一个指定标记的备忘录
originator.restoreMemento(caretaker.getMemento("001"));
}
}
注意:要严格限定备忘录的创建,建议增加 MAP的上限,否则系统很容易产生内存溢出情况。
6.4 更好的封装:双接口(一个类可以实现多个接口,在系统设计时,如果考虑对象的安全问题,可以提供两个接口,一个是业务的正常接口,实现必要的业务逻辑,叫做宽接口;另外一个接口是一个空接口,什么方法都没有,其目的是提供给子系统外的模块访问,因此较安全。)
类图如下:
源代码如下:
public class Originator {
// 内部状态
private String state = "";
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
// 创建一个备忘录
public IMemento createMemento() {
return new Memento(this.state);
}
// 恢复一个备忘录
public void restoreMemento(IMemento _memento) {
this.setState(((Memento) _memento).getState());
}
// 内置类
private class Memento implements IMemento {
// 发起人的内部状态
private String state = "";
// 构造函数传递参数
private Memento(String _state) {
this.state = _state;
}
private String getState() {
return state;
}
private void setState(String state) {
this.state = state;
}
}
}
public interface IMemento {
}
public class Caretaker {
// 备忘录对象
private IMemento memento;
public IMemento getMemento() {
return memento;
}
public void setMemento(IMemento memento) {
this.memento = memento;
}
}
public class Client {
public static void main(String[] args) {
// 定义出发起人
Originator originator = new Originator();
// 定义出备忘录管理员
Caretaker caretaker = new Caretaker();
// 创建一个备忘录
caretaker.setMemento(originator.createMemento());
// 恢复一个备忘录
originator.restoreMemento(caretaker.getMemento());
}
}
内置类Memento全部是private访问权限,也就是说除了发起人外,别人休想访问到,如果要产生关联关系,就通过空接口进行。
7 范例
如上各小节。(皆出自作者原书)