《设计模式》之十七:备忘录模式
Memento Pattern 备忘录模式提供了一种弥补真实世界的方法,让”后悔药“在程序的世界中真实可行,其定义如下:
Without violating encapsulation, capture and externalize an object's internal state so that the object can be restored to this state later
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态。
通俗讲,备忘录模式就是一个对象的备份模式,提供了一种程序数据的备份方法。
备忘录模式的三个角色定义:
1,Originator发起人角色
记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。
2,Memento备忘录角色
负责存储Originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态
3,Caretaker备忘录管理员角色
对备忘录进行管理、保存和提供备忘录
通用模板代码:
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 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 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()); } }
备忘录模式的扩展,利用Java的clone方法可以将上述的三个角色融合到一个类中去:
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() { 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; } }
注意:使用Clone模式的备忘录模式,应该在比较简单的场景或者比较单一的场景,尽量不要与其他对象产生严重耦合关系,因为浅拷贝和深拷贝的关系,你懂得。
备忘录模式使用场景:
1,需要保存和恢复数据的相关状态场景
2,提供一个可以回滚rollback的操作,比如Word中的CTRL+Z组合键,浏览器中的后退键,文件管理器的backspace键等。
3,需要监控的副本场景中,监控一般都是备份一个主线程中的对象,然后由分析程序来分析。
4,数据库事务管理的备忘录模式
备忘录模式注意事项:
1,备忘录的生命周期,备忘录创建出来就要在最近的代码中使用,要主动管理它的生命周期,建立就使用,不适用就立即删除其引用,等垃圾回收器去回收内存
2,备忘录的性能,不要在频繁建立备份的场景中使用备忘录模式比如一个for循环中。
多状态的备忘录模式
上面所说的都只有一个状态state,这在实际项目中是很少出现的,多状态下,我们写个通用的模板:
public class BeanUtils { /** * 把bean所有属性放入HashMap中去 * @param bean * @return */ 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(); if (!fieldName.equalsIgnoreCase("class")) { Object fieldValue = getter.invoke(bean); result.put(fieldName, fieldValue); } } } catch (Exception e) { // 异常处理 } return result; } public static void restoreProp(Object bean, HashMap<String, Object> propMap) { try { // 获得Bean描述 BeanInfo beanInfo = Introspector.getBeanInfo(bean.getClass()); // 获取Bean属性描述 PropertyDescriptor[] descriptors = beanInfo.getPropertyDescriptors(); // 遍历所有属性 for (PropertyDescriptor des : descriptors) { // 属性名称 String fieldName = des.getName(); // 如果有这个属性 if (propMap.containsKey(fieldName)) { // 写属性方法 Method setter = des.getWriteMethod(); setter.invoke(bean, propMap.get(fieldName)); } } } catch (Exception e) { // 处理异常 System.out.println("fuck"); e.printStackTrace(); } } }
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 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 + "state2=" + state2 + "state3=" + state3; } }
public class Caretaker { // 备忘录对象 private Memento memento; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } }
场景类Client就不写了,太简单了。。。
多备份的备忘录:
系统管理员经常会遇到这种情况,不仅仅存在一个Backup而言,而是由很多保存点check point或者叫save point来保存各个时间戳或者各个阶段的备份。我们这里只需要修改下Caretaker和Client应用类即可:
public class Caretaker { // 备忘录容器 private HashMap<String, Memento> memMap = new HashMap<String, Memento>(); public Memento getMemento(String key) { return memMap.get(key); } public void setMemento(String key, Memento memento) { this.memMap.put(key, memento); } }
在Client类中,获取和设置备忘录的时候多传递一个参数key就行了,无需多讲了。
封装的更好点,完美设计:
在系统管理上,备份的数据是只读的,绝对不能修改的,那么我们的模式中的Memento角色就应该是只读的。
增加一个空的接口:
public interface IMemento { }
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(IMemento _memento) { BeanUtils.restoreProp(this, ((Memento)_memento).getStateMap()); } // 增加一个toString方法 @Override public String toString() { return "state1=" + state1 + "state2=" + state2 + "state3=" + state3; } /** * 内部类,为了就是Memento的只读性 */ private class Memento implements IMemento{ // 接受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 HashMap<String, IMemento> memMap = new HashMap<String, IMemento>(); public IMemento getMemento(String key) { return memMap.get(key); } public void setMemento(String key, IMemento memento) { this.memMap.put(key, memento); } }
最佳实践:
备忘录模式是我们设计上的”月光宝盒“,可以让我们回到需要的年代,是程序的后悔药。
所以我们在设计的时候不要使用数据库的临时表作为缓存备份数据了,虽然是一个简单的方法,但是它加大了数据库操作的频繁度,最好的解决办法就是使用备忘录模式。
本人博客已搬家,新地址为:http://yidao620c.github.io/