24.1 如此追女孩子, 你还不乐大家有没有看过尼古拉斯·凯奇主演的《Next》 (中文译名为《预见未来》 ) ? 尼古拉斯·凯奇饰演一个可以预视并且扭转未来的人, 其中有一个情节很是让人心动——男女主角见面的那段情节: Cris Johnson(尼古拉斯·凯奇饰演) 坐在咖啡吧台前, 看着离自己近在咫尺的Callie Ferris(朱莉安·摩尔饰演) , 计划着怎么认识这个命中注定的女人, 看Cris Johnson如何利用自己的特异功能:● Cris Johnson端着一杯咖啡走过去, 说“你好, 可以认识你吗? ”被拒绝, 恢复到坐在咖啡吧台前的状态。● 走过去询问是否可以搭车, 被拒绝, 恢复原状。● 帮助解决困境, 被拒绝, 恢复原状。● 采用嬉皮士的方式解决困境, 被拒绝, 恢复原状。● 帮助解决困境, 被打伤, 装可怜, Callie Ferris怜惜, 于是乎相识了。看看这是一件多么幸福的事情, 追求一个女生可以多次反复地实验, 直到找到好的方法和途径为止, 这估计是大多数男生都希望获得的特异功能。 想想看, 看到一个心仪的女生,我们若反复尝试, 总会有一个方法打动她的, 多美好的一件事。 现在我们还得回到现实生活, 我们来分析一下类似事情的经过:● 复制一个当前状态, 保留下来, 这个状态就是等会儿搭讪女孩子失败后要恢复的状态, 你不恢复原始状态, 这不就露馅儿了吗?● 每次试探性尝试失败后, 都必须恢复到这个原始状态。● N次试探总有一次成功吧, 成功以后即可走成功路线。想想看, 我们这里的场景中最重要的是哪一块? 对的, 是原始状态的保留和恢复这块,如何保留一个原始, 如何恢复一个原始状态才是最重要的, 那想想看, 我们应该怎么实现呢? 很简单呀, 我们可以定义一个中间变量, 保留这个原始状态。 我们先看看类图, 如图24-1所示。图24-1 男孩状态类图太简单的类图了, 我们来解释一下图中的状态state是什么意思, 在某一时间点的所有位置信息、 心理信息、 环境信息都属于状态, 我们这里用了一个标识性的名词state代表所有状态, 比如在追女孩子前心情是期待、 心理是焦躁不安等。 每一次去认识女孩子都是会发生状态变化的, 我们使用changeState方法来代替, 由于程序比较简单, 就没有编写接口, 我们来看实现, 如代码清单24-1所示。代码清单24-1 男孩状态类public class Boy {//男孩的状态private String state = "";//认识女孩子后状态肯定改变, 比如心情、 手中的花等public void changeState(){this.state = "心情可能很不好";}public String getState() {return state;}public void setState(String state) {this.state = state;}}程序是很简单, 主要的业务逻辑是在场景类中, 我们来看场景类是如何进行状态的保留、 恢复的, 如代码清单24-2所示。代码清单24-2 场景类public class Client {public static void main(String[] args) {//声明出主角Boy boy = new Boy();//初始化当前状态boy.setState("心情很棒! ");System.out.println("=====男孩现在的状态======");System.out.println(boy.getState());//需要记录下当前状态呀Boy backup = new Boy();backup.setState(boy.getState());//男孩去追女孩, 状态改变boy.changeState();System.out.println("\n=====男孩追女孩子后的状态======");System.out.println(boy.getState());//追女孩失败, 恢复原状boy.setState(backup.getState());System.out.println("\n=====男孩恢复后的状态======");System.out.println(boy.getState());}}程序运行结果如下所示:=====男孩现在的状态======心情很棒!=====男孩追女孩子后的状态======心情可能很不好=====男孩恢复后的状态======心情很棒!程序运行正确, 输出结果也是我们期望的, 但是结果正确并不表示程序是最优的, 我们来看看场景类Client, 它代表的是高层模块, 或者说是非“近亲”模块的调用者, 注意看backup变量的使用, 它对于高层模块完全是多余的, 为什么一个状态的保存和恢复要让高层模块来负责呢? 这应该是Boy类的职责, 而不应该让高层模块来完成, 也就是破坏了Boy类的封装, 或者说Boy类没有封装好, 它应该是把backup的定义容纳进来, 而不应该让高层模块来定义。问题我们已经知道了, 就是Boy类封装不够, 那我们应该如何修改呢? 如果在Boy类中再增加一个方法或者其他的内部类来保存这个状态, 则对单一职责原则是一种破坏, 想想看单一职责原则是怎么说的? 一个类的职责应该是单一的, Boy类本身的职责是追求女孩子,而保留和恢复原始状态则应该由另外一个类来承担, 那我们把这个类取名就叫做备忘录, 这和大家经常在桌面上贴的那个便签是一个概念, 分析到这里我们的思路已经非常清楚了, 我们来修改一下类图, 如图24-2所示。图24-2 完善后的男孩状态类图改动很小, 增加了一个新的类Memento, 负责状态的保存和备份; 同时, 在Boy类中增加了创建一份备忘录createMemento和恢复一个备忘录resotreMemento, 我们先来看Boy类的变化, 如代码清单24-3所示。代码清单24-3 改进后的男孩状态类public class Boy {//男孩的状态private String state = "";//认识女孩子后状态肯定改变, 比如心情、 手中的花等public void changeState(){this.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());}}注意看, 确实只增加了两个方法创建备份和恢复备份, 至于在什么时候创建备份和恢复备份则是由高层模块决定的。 我们再来看备忘录模块, 如代码清单24-4所示。代码清单24-4 备忘录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;}}这就是一个简单的JavaBean, 保留男孩当时的状态信息。 我们再来看场景类, 稍做修改, 如代码清单24-5所示。代码清单24-5 改进后的场景类public class Client {public static void main(String[] args) {//声明出主角Boy boy = new Boy();//初始化当前状态boy.setState("心情很棒! ");System.out.println("=====男孩现在的状态======");System.out.println(boy.getState());//需要记录下当前状态呀Memento mem = boy.createMemento();//男孩去追女孩, 状态改变boy.changeState();System.out.println("\n=====男孩追女孩子后的状态======");System.out.println(boy.getState());//追女孩失败, 恢复原状boy.restoreMemento(mem);System.out.println("\n=====男孩恢复后的状态======");System.out.println(boy.getState());}}运行结果保持相同, 虽然程序中不再重复定义Boy类的对象了, 但是我们还是要关心备忘录, 这对迪米特法则是一个亵渎, 它告诉我们只和朋友类通信, 那这个备忘录对象是我们必须要通信的朋友类吗? 对高层模块来说, 它最希望要做的就是创建一个备份点, 然后在需要的时候再恢复到这个备份点就成了, 它不用关心到底有没有备忘录这个类。 那根据这一指导思想, 我们就需要把备忘录类再包装一下, 怎么包装呢? 建立一个管理类, 就是管理这个备忘录, 如图24-3所示。图24-3 完整的男孩追女生类图又增加了一个JavaBean, Boy类和Memento没有任何改变, 不再赘述。 我们来看增加的备忘录管理类, 如代码清单24-6所示。代码清单24-6 备忘录管理者public class Caretaker {//备忘录对象private Memento memento;public Memento getMemento() {return memento;}public void setMemento(Memento memento) {this.memento = memento;}}这个太简单了, 非常纯粹的一个JavaBean, 甭管它多简单, 只要有用就成, 我们来看场景类如何调用, 如代码清单24-7所示。代码清单24-7 进一步改进后的场景类public class Client {public static void main(String[] args) {//声明出主角Boy boy = new Boy();//声明出备忘录的管理者Caretaker caretaker = new Caretaker();//初始化当前状态boy.setState("心情很棒! ");System.out.println("=====男孩现在的状态======");System.out.println(boy.getState());//需要记录下当前状态呀caretaker.setMemento(boy.createMemento());//男孩去追女孩, 状态改变boy.changeState();System.out.println("\n=====男孩追女孩子后的状态======");System.out.println(boy.getState());//追女孩失败, 恢复原状boy.restoreMemento(caretaker.getMemento());System.out.println("\n=====男孩恢复后的状态======");System.out.println(boy.getState());}}注意看黑体部分, 就修改了这么多, 看看程序的逻辑是不是清晰了很多, 需要备份的时候就创建一个备份, 然后丢给备忘录管理者进行管理, 要取的时候再从管理者手中拿到这个备份。 这个备份者就类似于一个备份的仓库管理员, 创建一个丢进去, 需要的时候再拿出来。 这就是备忘录模式。24.2 备忘录模式的定义备忘录模式(Memento Pattern) 提供了一种弥补真实世界缺陷的方法, 让“后悔药”在程序的世界中真实可行, 其定义如下:Without violating encapsulation,capture and externalize an object's internal state so that theobject can be restored to this state later.(在不破坏封装性的前提下, 捕获一个对象的内部状态, 并在该对象之外保存这个状态。 这样以后就可将该对象恢复到原先保存的状态。 )通俗地说, 备忘录模式就是一个对象的备份模式, 提供了一种程序数据的备份方法, 其通用类图如图24-4所示。图24-4 备忘录模式的通用类图我们来看看类图中的三个角色。● Originator发起人角色记录当前时刻的内部状态, 负责定义哪些属于备份范围的状态, 负责创建和恢复备忘录数据。● Memento备忘录角色负责存储Originator发起人对象的内部状态, 在需要的时候提供发起人需要的内部状态。● Caretaker备忘录管理员角色对备忘录进行管理、 保存和提供备忘录。备忘录模式的通用代码也非常简单, 我们先看发起人角色, 如代码清单24-8所示。代码清单24-8 发起人角色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());}}我相信你心里此刻有很多疑问, 比如状态是多个怎么办? 需要有多份备份怎么办? 如果你很着急的话, 请看24.4节, 但我建议你还是跟随我一步一步地走, 我们再来看备忘录角色, 如代码清单24-9所示。代码清单24-9 备忘录角色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;}}这是一个简单的JavaBean,备忘录管理者也是一个简单的JavaBean, 如代码清单24-10所示。代码清单24-10 备忘录管理员角色public class Caretaker {//备忘录对象private Memento memento;public Memento getMemento() {return memento;}public void setMemento(Memento memento) {this.memento = memento;}}这3个主要角色都很简单, 我们来看场景类如何调用, 如代码清单24-11所示。代码清单24-11 场景类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());}}备忘录模式就是这么简单, 真正使用备忘录模式的时候可比这复杂得多。24.3 备忘录模式的应用由于备忘录模式有太多的变形和处理方式, 每种方式都有它自己的优点和缺点, 标准的备忘录模式很难在项目中遇到, 基本上都有一些变换处理方式。 因此, 我们在使用备忘录模式时主要了解如何应用以及需要注意哪些事项就成了。24.3.1 备忘录模式的使用场景● 需要保存和恢复数据的相关状态场景。● 提供一个可回滚(rollback) 的操作; 比如Word中的CTRL+Z组合键, IE浏览器中的后退按钮, 文件管理器上的backspace键等。● 需要监控的副本场景中。 例如要监控一个对象的属性, 但是监控又不应该作为系统的主业务来调用, 它只是边缘应用, 即使出现监控不准、 错误报警也影响不大, 因此一般的做法是备份一个主线程中的对象, 然后由分析程序来分析。● 数据库连接的事务管理就是用的备忘录模式, 想想看, 如果你要实现一个JDBC驱动, 你怎么来实现事务? 还不是用备忘录模式嘛!24.3.2 备忘录模式的注意事项● 备忘录的生命期备忘录创建出来就要在“最近”的代码中使用, 要主动管理它的生命周期, 建立就要使用, 不使用就要立刻删除其引用, 等待垃圾回收器对它的回收处理。● 备忘录的性能不要在频繁建立备份的场景中使用备忘录模式(比如一个for循环中) , 原因有二: 一是控制不了备忘录建立的对象数量; 二是大对象的建立是要消耗资源的, 系统的性能需要考虑。 因此, 如果出现这样的代码, 设计师就应该好好想想怎么修改架构了。24.4 备忘录模式的扩展24.4.1 clone方式的备忘录大家还记得在第13章中讲的原型模式吗? 我们可以通过复制的方式产生一个对象的内部状态, 这是一个很好的办法, 发起人角色只要实现Cloneable就成, 比较简单, 我们来看类图, 如图24-5所示。图24-5 Clone方式的备忘录从类图上看, 发起人角色融合了发起人角色和备忘录角色, 具有双重功效, 如代码清单24-12所示。代码清单24-12 融合备忘录的发起人角色public class Originator implements Cloneable{//内部状态private String state = "";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());}//克隆当前对象@Overrideprotected Originator clone(){try {return (Originator)super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return null;}}增加了clone方法, 产生了一个备份对象, 需要使用的时候再还原, 我们再来看管理员角色, 如代码清单24-13所示。代码清单24-13 备忘录管理员角色public class Caretaker {//发起人对象private Originator originator;public Originator getOriginator() {return originator;}public void setOriginator(Originator originator) {this.originator = originator;}}没什么太大变化, 只是备忘录角色转换成了发起人角色, 还是一个简单的JavaBean。 我们来想想这种模式是不是还可以简化? 要管理员角色干什么? 就是为了管理备忘录角色, 现在连备忘录角色都被合并了, 还留着它干吗? 我们想办法把它也精简掉, 如代码清单24-14所示。代码清单24-14 发起人自主备份和恢复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());}//克隆当前对象@Overrideprotected Originator clone(){try {return (Originator)super.clone();} catch (CloneNotSupportedException e) {e.printStackTrace();}return null;}}可能你要发问了, 这和备忘录模式的定义不相符, 它定义是“在该对象之外保存这个状态”, 而你却把这个状态保存在了发起人内部。 是的, 设计模式定义的诞生比Java的出世略早, 它没有想到Java程序是这么有活力, 有远见, 而且在面向对象的设计中, 即使把一个类封装在另一个类中也是可以做到的, 何况一个小小的对象复制, 这是它的设计模式完全没有预见到的, 我们把它弥补回来。再来看看Client是如何调用的, 如代码清单24-15所示。代码清单24-15 场景类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方式的备忘录模式适用于较简单的场景。注意 使用Clone方式的备忘录模式, 可以使用在比较简单的场景或者比较单一的场景中, 尽量不要与其他的对象产生严重的耦合关系。24.4.2 多状态的备忘录模式读者应该看到我们以上讲解都是单状态的情况, 在实际的开发中一个对象不可能只有一个状态, 一个JavaBean有多个属性非常常见, 这都是它的状态, 如果照搬我们以上讲解的备忘录模式, 是不是就要写一堆的状态备份、 还原语句? 这不是一个好办法, 这种类似的非智力劳动越多, 犯错误的几率越大, 那我们有什么办法来处理多个状态的备份问题呢?下面我们来讲解一个对象全状态备份方案, 它有多种处理方式, 比如使用Clone的方式就可以解决, 使用数据技术也可以解决(DTO回写到临时表中) 等, 我们要讲的方案就对备忘录模式继续扩展一下, 实现一个JavaBean对象的所有状态的备份和还原, 如图24-6所示。图24-6 多状态的备忘录模式还是比较简单的类图, 增加了一个BeanUtils类, 其中backupProp是把发起人的所有属性值转换到HashMap中, 方便备忘录角色存储; restoreProp方法则是把HashMap中的值返回到发起人角色中。 可能各位要说了, 为什么要使用HashMap, 直接使用Originator对象的拷贝不是一个很好的方法吗? 可以这样做, 你就破坏了发起人的通用性, 你在做恢复动作的时候需要对该对象进行多次赋值操作, 也容易产生错误。 我们先来看发起人角色, 如代码清单24-16所示。代码清单24-16 发起人角色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方法@Overridepublic String toString(){return "state1=" +state1+"\nstat2="+state2+"\nstate3="+state3;}}覆写toString方法是为了方便打印, 可以让展示的结果更清晰。 我们再来看BeanUtils工具类, 如代码清单24-17所示。代码清单24-17 BeanUtils工具类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.getPropertyDescrip//遍历所有属性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();}}}该类大家在项目中会经常用到, 可以作为参考使用。 类似的功能有很多工具已经提供,比如Spring、 Apache工具集commons等, 大家也可以直接使用。 我们再来看备忘录角色, 如代码清单24-18所示。代码清单24-18 备忘录角色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;}}我们再编写一个场景类, 看看我们的成果是否正确, 如代码清单24-19所示。代码清单24-19 场景类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);}}运行结果如下所示:===初始化状态===state1=中国stat2=强盛state3=繁荣===修改后状态===state1=软件stat2=架构state3=优秀===恢复后状态===state1=中国stat2=强盛state3=繁荣通过这种方式的改造, 不管有多少状态都没有问题, 直接把原有的对象所有属性都备份了一遍, 想恢复当时的点数据? 那太容易了!注意 如果要设计一个在运行期决定备份状态的框架, 则建议采用AOP框架来实现, 避免采用动态代理无谓地增加程序逻辑复杂性。24.4.3 多备份的备忘录不知道你有没有做过系统级别的维护? 比如Backup Administrator(备份管理员) , 每天负责查看系统的备份情况, 所有的备份都是由自动化脚本产生的。 有一天, 突然有一个重要的系统说我数据库有点问题, 请把上一个月末的数据拉出来恢复, 那怎么办? 对备份管理员来说, 这很好办, 直接根据时间戳找到这个备份, 还原回去就成了, 但是对于我们刚刚学习的备忘录模式却行不通, 为什么呢? 它对于一个确定的发起人, 永远只有一份备份, 在这种情况下, 单一的备份就不能满足要求了, 我们需要设计一套多备份的架构。我们先来说一个名词, 检查点(Check Point) , 也就是你在备份的时候做的戳记, 系统级的备份一般是时间戳, 那我们程序的检查点该怎么设计呢? 一般是一个有意义的字符串。我们只要把通用代码中的Caretaker管理员稍做修改就可以了, 如代码清单24-20所示。代码清单24-20 备忘录管理员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);}}把容纳备忘录的容器修改为Map类型就可以了, 场景类也稍做改动, 如代码清单24-21所示。代码清单24-21 场景类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的上限, 否则系统很容易产生内存溢出情况。24.4.4 封装得更好一点在系统管理上, 一个备份的数据是完全、 绝对不能修改的, 它保证数据的洁净, 避免数据污染而使备份失去意义。 在我们的设计领域中, 也存在着同样的问题, 备份是不能被篡改的, 也就是说需要缩小备份出的备忘录的阅读权限, 保证只能是发起人可读就成了, 那怎么才能做到这一点呢? 使用内置类, 如图24-7所示。图24-7 使用内置类的备忘录模式这也是比较简单的, 建立一个空接口IMemento——什么方法属性都没有的接口, 然后在发起人Originator类中建立一个内置类(也叫做类中类) Memento实现IMemento接口, 同时也实现自己的业务逻辑, 如代码清单24-22所示。代码清单24-22 发起人角色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;}}}内置类Memento全部是private的访问权限, 也就是说除了发起人外, 别人休想访问到,那如果要产生关联关系又应如何处理呢? 通过接口! 别忘记了我们还有一个空接口是公共的访问权限, 如代码清单24-23所示。代码清单24-23 备忘录的空接口public interface IMemento {}我们再来看管理者, 如代码清单24-24所示。代码清单24-24 备忘录管理者public class Caretaker {//备忘录对象private IMemento memento;public IMemento getMemento() {return memento;}public void setMemento(IMemento memento) {this.memento = memento;}}全部通过接口访问, 这当然没有问题, 如果你想访问它的属性那是肯定不行的。 但是安全是相对的, 没有绝对的安全, 可以使用refelect反射修改Memento的数据。在这里我们使用了一个新的设计方法: 双接口设计, 我们的一个类可以实现多个接口,在系统设计时, 如果考虑对象的安全问题, 则可以提供两个接口, 一个是业务的正常接口,实现必要的业务逻辑, 叫做宽接口; 另外一个接口是一个空接口, 什么方法都没有, 其目的是提供给子系统外的模块访问, 比如容器对象, 这个叫做窄接口, 由于窄接口中没有提供任何操纵数据的方法, 因此相对来说比较安全。24.5 最佳实践备忘录模式是我们设计上“月光宝盒”, 可以让我们回到需要的年代; 是程序数据的“后悔药”, 吃了它就可以返回上一个状态; 是设计人员的定心丸, 确保即使在最坏的情况下也能获得最近的对象状态。 如果大家看懂了的话, 请各位在设计的时候就不要使用数据库的临时表作为缓存备份数据了, 虽然是一个简单的办法, 但是它加大了数据库操作的频繁度, 把压力下放到数据库了, 最好的解决办法就是使用备忘录模式。
来源:https://www.cnblogs.com/gendway/p/11857813.html