设计模式笔记--备忘录模式
2016-01-13 13:54
330 查看
常用设计模式有23中,分为:
创建型模式(主要用于创建对象)
1、单例模式 2、工厂方法模式
3、抽象工厂模式 4、建造者模式 5、原型模式
行为型模式 (主要用于描述对象或类是怎样交互和怎样分配职责)
1、模板方法模式 2、中介者模式 3、命令模式
4、责任链模式 5、策略模式 6、迭代器模式
7、观察者模式 8、备忘录模式 9、访问者模式 10、状态模式 11、解释器模式
结构型模式(主要用于处理类或对象的组合)
1、代理模式 2、装饰模式
3、适配器模式 4、组合模式 5、外观模式(门面模式) 6、享元模式
7、桥梁模式
备忘录模式
备忘录模式(Memento Pattern)提供了一种弥补真实世界缺陷的方法,让“后悔药”在程序的世界中真实可行,其定义如下:
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态
三个角色。
● Originator发起人角色
记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。
● Memento备忘录角色
负责存储Originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。
● Caretaker备忘录管理员角色
对备忘录进行管理、保存和提供备忘录。
备忘录模式的通用代码
发起人角色,如代码清单24-8所示。
我们再来看备忘录角色,如代码清单24-9所示。
备忘录管理者也是一个简单的JavaBean,如代码清单24-10所示。
我们来看场景类如何调用,如代码清单24-11所示。
备忘录模式就是这么简单,真正使用备忘录模式的时候可比这复杂得多。
由于备忘录模式有太多的变形和处理方式,每种方式都有它自己的优点和缺点,标准的备忘录模式很难在项目中遇到,基本上都有一些变换处理方式。因此,我们在使用备忘录模式时主要了解如何应用以及需要注意哪些事项就成了。
使用场景
● 需要保存和恢复数据的相关状态场景。
● 提供一个可回滚(rollback)的操作;比如Word中的CTRL+Z组合键,IE浏览器中的后退按钮,文件管理器上的backspace键等。
● 需要监控的副本场景中。
● 数据库连接的事务管理就是用的备忘录模式
注意事项
● 备忘录的生命期
备忘录创建出来就要在“最近”的代码中使用,要主动管理它的生命周期,建立就要使用,不使用就要立刻删除其引用,等待垃圾回收器对它的回收处理。
● 备忘录的性能
不要在频繁建立备份的场景中使用备忘录模式(比如一个for循环中)
备忘录模式的扩展
1)clone方式的备忘录
从类图上看,发起人角色融合了发起人角色和备忘录角色,具有双重功效,如代码清单24-12所示。
增加了clone方法,产生了一个备份对象,需要使用的时候再还原,
管理员角色,如代码清单24-13所示。
没什么太大变化,只是备忘录角色转换成了发起人角色,还是一个简单的JavaBean。
我们来想想这种模式是不是还可以简化?要管理员角色干什么?就是为了管理备忘录角色,现在连备忘录角色都被合并了,还留着它干吗?我们想办法把它也精简掉,如代码清单24-14所示。
可能你要发问了,这和备忘录模式的定义不相符,它定义是“在该对象之外保存这个状态”,而你却把这个状态保存在了发起人内部。
是的,设计模式定义的诞生比Java的出世略早,它没有想到Java程序是这么有活力,有远见,而且在面向对象的设计中,即使把一个类封装在另一个类中也是可以做到的,何况一个小小的对象复制,这是它的设计模式完全没有预见到的,我们把它弥补回来。
再来看看Client是如何调用的,如代码清单24-15所示。
程序精简了很多,而且高层模块的依赖也减少了 ,考虑一下原型模式深拷贝和浅拷贝的问题,在复杂的场景下它会让你的程序逻辑异常混乱,
注意 使用Clone方式的备忘录模式,可以使用在比较简单的场景或者比较单一的场景中,尽量不要与其他的对象产生严重的耦合关系。
2)多状态的备忘录模式
还是比较简单的类图,增加了一个BeanUtils类,其中
backupProp是把发起人的所有属性值转换到HashMap中,方便备忘录角色存储;
restoreProp方法则是把HashMap中的值返回到发起人角色中。
为什么要使用HashMap,直接使用Originator对象的拷贝不是一个很好的方法吗?
可以这样做,你就破坏了发起人的通用性,你在做恢复动作的时候需要对该对象进行多次赋值操作,也容易产生错误
代码清单
我们再来看BeanUtils工具类,如代码清单24-17所示。
该类大家在项目中会经常用到,可以作为参考使用。类似的功能有很多工具已经提供,比如Spring、Apache工具集commons等,大家也可以直接使用。
我们再来看备忘录角色,如代码清单24-18所示。
我们再编写一个场景类,看看我们的成果是否正确,如代码清单24-19所示。
注意 如果要设计一个在运行期决定备份状态的框架,则建议采用AOP框架来实现,避免采用动态代理无谓地增加程序逻辑复杂性。
3)多备份的备忘录
检查点(Check Point),也就是你在备份的时候做的戳记,系统级的备份一般是时间戳,
我们只要把通用代码中的Caretaker管理员稍做修改就可以了,如代码清单24-20所示。
把容纳备忘录的容器修改为Map类型就可以了,场景类也稍做改动,如代码清单24-21所示。
注意 内存溢出问题,该备份一旦产生就装入内存,没有任何销毁的意向,这是非常危险的。因此,在系统设计时,要严格限定备忘录的创建,建议增加Map的上限,否则系统很
容易产生内存溢出情况。
4)封装得更好一点
一个备份的数据是完全、绝对不能修改的,它保证数据的洁净,避免数据污染而使备份失去意义
备份是不能被篡改的,也就是说需要缩小备份出的备忘录的阅读权限,保证只能是发起人可读就成
这也是比较简单的,建立一个空接口IMemento——什么方法属性都没有的接口,然后在发起人Originator类中建立一个内置类(也叫做类中类)Memento实现IMemento接口,同时也实现自己的业务逻辑,如代码清单24-22所示。
内置类Memento全部是private的访问权限,也就是说除了发起人外,别人休想访问到,
那如果要产生关联关系又应如何处理呢?
通过接口!别忘记了我们还有一个空接口是公共的访问权限,
我们再来看管理者,如代码清单24-24所示。
全部通过接口访问,这当然没有问题,如果你想访问它的属性那是肯定不行的。
但是安全是相对的,没有绝对的安全,可以使用refelect反射修改Memento的数据。
在这里我们使用了一个新的设计方法:双接口设计,我们的一个类可以实现多个接口,在系统设计时,如果考虑对象的安全问题,则可以提供两个接口,
一个是业务的正常接口,实现必要的业务逻辑,叫做宽接口;
另外一个接口是一个空接口,什么方法都没有,其目的是提供给子系统外的模块访问,比如容器对象,这个叫做窄接口,由于窄接口中没有提供任何操纵数据的方法,因此相对来说比较安全。
创建型模式(主要用于创建对象)
1、单例模式 2、工厂方法模式
3、抽象工厂模式 4、建造者模式 5、原型模式
行为型模式 (主要用于描述对象或类是怎样交互和怎样分配职责)
1、模板方法模式 2、中介者模式 3、命令模式
4、责任链模式 5、策略模式 6、迭代器模式
7、观察者模式 8、备忘录模式 9、访问者模式 10、状态模式 11、解释器模式
结构型模式(主要用于处理类或对象的组合)
1、代理模式 2、装饰模式
3、适配器模式 4、组合模式 5、外观模式(门面模式) 6、享元模式
7、桥梁模式
备忘录模式
备忘录模式(Memento Pattern)提供了一种弥补真实世界缺陷的方法,让“后悔药”在程序的世界中真实可行,其定义如下:
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态
三个角色。
● Originator发起人角色
记录当前时刻的内部状态,负责定义哪些属于备份范围的状态,负责创建和恢复备忘录数据。
● Memento备忘录角色
负责存储Originator发起人对象的内部状态,在需要的时候提供发起人需要的内部状态。
● Caretaker备忘录管理员角色
对备忘录进行管理、保存和提供备忘录。
备忘录模式的通用代码
发起人角色,如代码清单24-8所示。
<span style="font-size:18px;">代码清单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()); } }</span>
我们再来看备忘录角色,如代码清单24-9所示。
<span style="font-size:18px;">代码清单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; } }</span>这是一个简单的JavaBean,
备忘录管理者也是一个简单的JavaBean,如代码清单24-10所示。
<span style="font-size:18px;">代码清单24-10 备忘录管理员角色 public class Caretaker { //备忘录对象 private Memento memento; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } }</span>
我们来看场景类如何调用,如代码清单24-11所示。
<span style="font-size:18px;">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()); } }</span>
备忘录模式就是这么简单,真正使用备忘录模式的时候可比这复杂得多。
由于备忘录模式有太多的变形和处理方式,每种方式都有它自己的优点和缺点,标准的备忘录模式很难在项目中遇到,基本上都有一些变换处理方式。因此,我们在使用备忘录模式时主要了解如何应用以及需要注意哪些事项就成了。
使用场景
● 需要保存和恢复数据的相关状态场景。
● 提供一个可回滚(rollback)的操作;比如Word中的CTRL+Z组合键,IE浏览器中的后退按钮,文件管理器上的backspace键等。
● 需要监控的副本场景中。
● 数据库连接的事务管理就是用的备忘录模式
注意事项
● 备忘录的生命期
备忘录创建出来就要在“最近”的代码中使用,要主动管理它的生命周期,建立就要使用,不使用就要立刻删除其引用,等待垃圾回收器对它的回收处理。
● 备忘录的性能
不要在频繁建立备份的场景中使用备忘录模式(比如一个for循环中)
备忘录模式的扩展
1)clone方式的备忘录
从类图上看,发起人角色融合了发起人角色和备忘录角色,具有双重功效,如代码清单24-12所示。
<span style="font-size:18px;">代码清单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()); } //克隆当前对象 @Override protected Originator clone(){ try { return (Originator)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } }</span>
增加了clone方法,产生了一个备份对象,需要使用的时候再还原,
管理员角色,如代码清单24-13所示。
<span style="font-size:18px;">代码清单24-13 备忘录管理员角色 public class Caretaker { //发起人对象 private Originator originator; public Originator getOriginator() { return originator; } public void setOriginator(Originator originator) { this.originator = originator; } }</span>
没什么太大变化,只是备忘录角色转换成了发起人角色,还是一个简单的JavaBean。
我们来想想这种模式是不是还可以简化?要管理员角色干什么?就是为了管理备忘录角色,现在连备忘录角色都被合并了,还留着它干吗?我们想办法把它也精简掉,如代码清单24-14所示。
<span style="font-size:18px;">代码清单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()); } //克隆当前对象 @Override protected Originator clone(){ try { return (Originator)super.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } return null; } }</span>
可能你要发问了,这和备忘录模式的定义不相符,它定义是“在该对象之外保存这个状态”,而你却把这个状态保存在了发起人内部。
是的,设计模式定义的诞生比Java的出世略早,它没有想到Java程序是这么有活力,有远见,而且在面向对象的设计中,即使把一个类封装在另一个类中也是可以做到的,何况一个小小的对象复制,这是它的设计模式完全没有预见到的,我们把它弥补回来。
再来看看Client是如何调用的,如代码清单24-15所示。
<span style="font-size:18px;">代码清单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()); } } </span>
程序精简了很多,而且高层模块的依赖也减少了 ,考虑一下原型模式深拷贝和浅拷贝的问题,在复杂的场景下它会让你的程序逻辑异常混乱,
注意 使用Clone方式的备忘录模式,可以使用在比较简单的场景或者比较单一的场景中,尽量不要与其他的对象产生严重的耦合关系。
2)多状态的备忘录模式
还是比较简单的类图,增加了一个BeanUtils类,其中
backupProp是把发起人的所有属性值转换到HashMap中,方便备忘录角色存储;
restoreProp方法则是把HashMap中的值返回到发起人角色中。
为什么要使用HashMap,直接使用Originator对象的拷贝不是一个很好的方法吗?
可以这样做,你就破坏了发起人的通用性,你在做恢复动作的时候需要对该对象进行多次赋值操作,也容易产生错误
代码清单
<span style="font-size:18px;">代码清单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方法 @Override public String toString(){ return "state1=" +state1+"\nstat2="+state2+"\nstate3="+state3; } }</span>
我们再来看BeanUtils工具类,如代码清单24-17所示。
<span style="font-size:18px;">代码清单24-17 BeanUtils工具类 public class BeanUtils { //把bean的所有属性及数值放入到Hashmap中 public static HashMap backupProp(Object bean){ HashMap result = new HashMap(); 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 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(); } } }</span>
该类大家在项目中会经常用到,可以作为参考使用。类似的功能有很多工具已经提供,比如Spring、Apache工具集commons等,大家也可以直接使用。
我们再来看备忘录角色,如代码清单24-18所示。
<span style="font-size:18px;">代码清单24-18 备忘录角色 public class Memento { //接受HashMap作为状态 private HashMap stateMap; //接受一个对象,建立一个备份 public Memento(HashMap map){ this.stateMap = map; } public HashMap getStateMap() { return stateMap; } public void setStateMap(HashMap stateMap) { this.stateMap = stateMap; } }</span>
我们再编写一个场景类,看看我们的成果是否正确,如代码清单24-19所示。
<span style="font-size:18px;">代码清单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); } } </span>
注意 如果要设计一个在运行期决定备份状态的框架,则建议采用AOP框架来实现,避免采用动态代理无谓地增加程序逻辑复杂性。
3)多备份的备忘录
检查点(Check Point),也就是你在备份的时候做的戳记,系统级的备份一般是时间戳,
我们只要把通用代码中的Caretaker管理员稍做修改就可以了,如代码清单24-20所示。
<span style="font-size:18px;">代码清单24-20 备忘录管理员 public class Caretaker { //容纳备忘录的容器 private HashMap memMap = new HashMap(); public Memento getMemento(String idx) { return memMap.get(idx); } public void setMemento(String idx,Memento memento) { this.memMap.put(idx, memento); } }</span>
把容纳备忘录的容器修改为Map类型就可以了,场景类也稍做改动,如代码清单24-21所示。
<span style="font-size:18px;">代码清单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")); } } </span>
注意 内存溢出问题,该备份一旦产生就装入内存,没有任何销毁的意向,这是非常危险的。因此,在系统设计时,要严格限定备忘录的创建,建议增加Map的上限,否则系统很
容易产生内存溢出情况。
4)封装得更好一点
一个备份的数据是完全、绝对不能修改的,它保证数据的洁净,避免数据污染而使备份失去意义
备份是不能被篡改的,也就是说需要缩小备份出的备忘录的阅读权限,保证只能是发起人可读就成
这也是比较简单的,建立一个空接口IMemento——什么方法属性都没有的接口,然后在发起人Originator类中建立一个内置类(也叫做类中类)Memento实现IMemento接口,同时也实现自己的业务逻辑,如代码清单24-22所示。
<span style="font-size:18px;">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; } } }</span>
内置类Memento全部是private的访问权限,也就是说除了发起人外,别人休想访问到,
那如果要产生关联关系又应如何处理呢?
通过接口!别忘记了我们还有一个空接口是公共的访问权限,
<span style="font-size:18px;">代码清单24-23 备忘录的空接口 public interface IMemento { }</span>
我们再来看管理者,如代码清单24-24所示。
<span style="font-size:18px;">代码清单24-24 备忘录管理者 public class Caretaker { //备忘录对象 private IMemento memento; public IMemento getMemento() { return memento; } public void setMemento(IMemento memento) { this.memento = memento; } }</span>
全部通过接口访问,这当然没有问题,如果你想访问它的属性那是肯定不行的。
但是安全是相对的,没有绝对的安全,可以使用refelect反射修改Memento的数据。
在这里我们使用了一个新的设计方法:双接口设计,我们的一个类可以实现多个接口,在系统设计时,如果考虑对象的安全问题,则可以提供两个接口,
一个是业务的正常接口,实现必要的业务逻辑,叫做宽接口;
另外一个接口是一个空接口,什么方法都没有,其目的是提供给子系统外的模块访问,比如容器对象,这个叫做窄接口,由于窄接口中没有提供任何操纵数据的方法,因此相对来说比较安全。
相关文章推荐
- 1038. Recover the Smallest Number (30) - 字符串排序
- Git中三种文件状态及其转换
- Java Web(10)在Ubuntu 15.10 下安装mysql设置数据库编码
- 数据库置疑(可疑状态)的修复方法
- android 下载apk源码
- 说一说golang的协程
- oracle数据类型和对应的java类型
- android中延迟执行某个任务
- svn服务器端搭建及自动更新项目文件
- Theano+Keras+CUDA7.5+VS2013+Windows10x64配置
- mysql的replace into类似于oracle的merge sql语句
- classpath和环境变量设置
- jsp---DBUtil用法之ResultSetHandler 和 QueryRunner总结
- java基本类型的长度
- Linux Kickstart无人值守安装
- [Asp.net 5] Caching-缓存架构与源码分析
- linux mysql远程连接
- CSS+DIV之强化background属性
- Objective-C 关于找到所在的视图控制器
- 通过Navicat for MySQL恢复数据 mysql [Err] 2006 - MySQL server has gone away [Err] INSERT INTO错误