《大设》第二十二篇之备忘录模式
又是好几天没有写博客了,好尴尬,今天就不国际惯例了,直接进入正文吧。
每次在提交代码的时候,跟服务器一同步就看到好几个红色的冲突文件,年轻的时候总以为有冲突不可怕,先更新下来再慢慢解决冲突吧,直到又一次更新下来之后整个都玩炸了才想到没有备份,只好又辛苦的把前面写的又改了一遍,幸好当时只有一个文件,要是多了,那就真的炸了,所以现在我养成了备份的习惯,每次提交前都会做个备份再和服务器合并文件。
难道这个又是一个设计模式吗?聪明,这就是我们今天要说的备忘录模式,顾名思义就是给文件做个备份,如果当前的文件出现问题了你就可以使用备份文件恢复过来而不是重新去搞一份。
那么我们先来看一下百度百科对于备忘录模式的定义:
在不破坏封闭的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将对象恢复到保存的状态。
根据定义我们可以这样理解,也就是现在有一个对象,对象中存在有状态,我们可以某一时刻获取到这个对象的内部状态,并且将这些对象保存在当前对象之外,当然这是需要在不破坏原有对象的封装的前提下才能进行的,如果使用反射等破坏封装的技术,那就没法玩了。 我们通过类图来看一下备忘录模式中的各个角色:
备忘录模式中的角色主要分为三个:
- Originator(发起人)角色:负责创建一个备忘录,用以记录当前时刻自身的内部状态,并可使用备忘录恢复内部状态。发起人可以决定在备忘录中存储自身的哪些状态。
- Memento(备忘录)角色:负责存储发起人的内部状态,并防止除发起人对象之外的对象访问备忘录。
- Caretaker(负责人)角色:负责人也可以称之为管理者,负责保存备忘录,但是并不能对备忘录的进行操作,在负责人对象中可以存储一个到多个备忘录,但是负责人仅仅负责存储,不负责修改备忘录,也不需要知道备忘录的详细细节。
通过上面的定义我们知道,在备忘录中存储了某一时刻发起人对象中的一些状态,这些状态属于中间状态,试想一下,在发起人保存了一个状态之后生成了一个备忘录,但是有其他对象改变了备忘录中的状态,这时候会出现什么情况?
很显然,这个备忘录就不能用来使发起人对象恢复到原来的状态,那么也就是说备忘录只能由发起人对象来调用,也就是不允许除发起人对象之外的对象访问备忘录的构造方法或者相关方法。
那么如何实现上面所说的只能由发起人类来访问备忘录对象呢?在JAVA中,可以将备忘录类设计成使用默认的访问标识符,并且将发起人类和备忘录类封装在同一个包下面,通过包内可见来达到只能由发起人类访问备忘录类的目的。
当然还有一个方法,就是将备忘录类设计成发起人类的内部类,这样也能保证只有发起人类才能访问备忘录类,而其他对象不能访问备忘录类中的属性和方法(原因:内部类提供了更好的封装,除了该外围类,其他类都不能访问)。
前面我们介绍了备忘录模式的定义和类图,接下来我们来看一下备忘录模式的简单实现:
首先是发起人类:
package com.pattern; public class Originator { private String state1; private String state2; /** * 构造方法 */ public Originator(){ super(); } /** * 构造方法 * @param state1 * @param state2 */ public Originator(String state1,String state2){ super(); this.state1 = state1; this.state2 = state2; } /** * 创建一个备忘录对象 * @return */ public Memento createMemento(){ return new Memento(this); } /** * 恢复发起人对象到原来的状态 * @param m */ public void restoreMemento(Memento m){ this.state1 = m.getState1(); this.state2 = m.getState2(); } 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; } }
备忘录类,注意在同一个包下,也可以是内部类:
package com.pattern; class Memento { private String state1; private String state2; /** * 构造方法 * @param o 发起人对象 */ public Memento(Originator o){ super(); state1 = o.getState1(); state2 = o.getState2(); } 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; } }
还需要一个负责人类:
package com.pattern; public class CareTaker { private Memento memento; public Memento getMemento() { return memento; } public void setMemento(Memento memento) { this.memento = memento; } }
测试类如下所示:
public static void main(String[] args) { Originator originator = new Originator("状态1", "状态2"); System.out.println("原始状态:"+originator.getState1()+" "+originator.getState2()); System.out.println("创建备忘录"); Memento memento = originator.createMemento(); CareTaker careTaker = new CareTaker(); careTaker.setMemento(memento); originator.setState1("修改状态1"); originator.setState2("修改状态2"); System.out.println("改变原有对象的状态为:"+originator.getState1()+" "+originator.getState2()); System.out.println("恢复到原来的状态"); originator.restoreMemento(careTaker.getMemento()); System.out.println("恢复后的状态:"+originator.getState1()+" "+originator.getState2()); }
测试结果:
上面的代码是备忘录模式的简单实现,而且只有两个属性需要保存,但是我们平时在使用的时候,一般是一个java bean ,如果其中的属性比较少,那么可以使用上面的方式,但是如果需要保存的属性(状态)比较多,使用上面的方式,在发起人类中的restoreMemento()方法和备忘录类中的构造方法中赋值时都比较麻烦。
针对上面的问题,可以分为两种情况来说:
首先,如果发起人类中的属性都是对象,此时可以让这些成员对象都实现Cloneable接口,重写clone方法,这样在备忘录对象中设置属性时就可以直接使用对象的clone方法复制对象了,当然这种方法也是有缺陷的,比如某个类不能修改其源代码,但是还是需要保存,这就有点尴尬了。
其次,就是发起人类中都是单纯的属性,但是属性比较多,并且各个属性都需要保存,这时候怎么办呢?这时候可以在备忘录类中使用Map的方式保存需要保存的对象,map的键是属性名,值是属性的值,然后在createMemento()方法中传入这样的Map对象即可,同样,如果需要保存多个备忘录对象,那么也只是需要在Caretaker负责人对象中,使用Map或者List的方式存储多个备忘录对象,然后根据键或者下标取出对应的值,我从别人的博客里面偷来了一段代码,大家可以看一下(原文地址):
package com.pattern; import java.beans.BeanInfo; import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; 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()); } public String toString(){ return "state1="+state1+"state2="+state2+"state3="+state3; } } class Memento { private Map<String, Object> stateMap; public Memento(Map<String, Object> map){ this.stateMap = map; } public Map<String, Object> getStateMap() { return stateMap; } public void setStateMap(Map<String, Object> stateMap) { this.stateMap = stateMap; } } class BeanUtils { public static Map<String, Object> backupProp(Object bean){ Map<String, Object> result = new HashMap<String, Object>(); try{ 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){ e.printStackTrace(); } return result; } public static void restoreProp(Object bean, Map<String, Object> propMap){ try { 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) { e.printStackTrace(); } } } class Caretaker { private Map<String, Memento> memMap = new HashMap<String, Memento>(); public Memento getMemento(String index){ return memMap.get(index); } public void setMemento(String index, Memento memento){ this.memMap.put(index, memento); } } 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("001",ori.createMemento()); ori.setState1("软件"); ori.setState2("架构"); ori.setState3("优秀"); System.out.println("===修改后状态===\n"+ori); ori.restoreMemento(caretaker.getMemento("001")); System.out.println("===恢复后状态===\n"+ori); } }
上面的代码是从别的大神的博客中偷过来的,地址上面也给了,之所以要偷上面的代码,除了解决了上面所说的多属性多备忘录之外,我还学到了BeanInfo这个类的使用,以前还真的没有用过这个类,虽然也会用反射,但是还真不会用这个类,这也算是在学习设计模式的时候补习一下基础知识吧。
备忘录模式的代码说完了,我们来简单总结一下这个设计模式:
首先,备忘录模式提供了一种状态恢复的机制,使得当前的对象状态失效或者出现问题时可以轻松的恢复到历史的某个状态。
其次,备忘录模式实现了对信息的封装,备忘录对象封装了发起人对象的一些状态,在不破坏封闭的前提下,这些状态不会被其他对象所改变,只能由发起人对象来使用。
第三,通过在负责人对象中使用集合的方式可以存储发起人对象的多个状态,用户可以选择恢复到历史的某个状态;
上面所说的都是备忘录模式的优点,同时这个模式也存在有缺点:
我们知道备忘录就是把发起人对象的状态复制了一份,如果这个发起人对象中的属性特别多,那么在复制后所消耗的资源也会很多,也就是需要占用大量的内存,在备份多个状态时,所消耗的资源又会进一步的提高,总之,使用备忘录模式是会大量的消耗内存资源的。
那么备忘录模式什么时候使用呢?
第一,在保存一个对象在某一时刻的状态时使用,通过备份这一时刻的状态,用户可以轻松的恢复到当前的状态;
第二,防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象;
OK,备忘录模式暂时就到这里了,下篇再见!!!
参考文章
- http://blog.csdn.net/lovelion/article/details/7526759
- http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=29140694&id=4127905
- http://blog.csdn.net/zhengzhb/article/details/7697549