按照定义中的要求,备忘录角色要保持完整的封装。最好的情况便是:备忘录角色只应该暴露操作内部存储属性的的接口给“备忘发起角色”。而对于其他角色则是不可见的。GOF在书中以C++为例进行了探讨。但是在Java中没有提供类似于C++中友元的概念。
在Java中怎样才能保持备忘录角色的封装呢?
下面对三种在Java中可保存封装的方法进行探讨。
第一种就是采用两个不同的接口类来限制访问权限。这两个接口类中,一个提供比较完备的操作状态的方法,我们称它为宽接口;而另一个则可以只是一个标示,我们称它为窄接口。备忘录角色要实现这两个接口类。这样对于“备忘发起角色”采用宽接口进行访问,而对于其他的角色或者对象则采用窄接口进行访问。
这种实现比较简单,但是需要人为的进行规范约束——而这往往是没有力度的。
第二种方法便很好的解决了第一种的缺陷:采用内部类来控制访问权限。将备忘录角色作为“备忘发起角色”的一个私有内部类。好处我不详细解释了,看看代码吧就明白了。下面的代码是一个完整的备忘录模式的教学程序。它便采用了第二种方法来实现备忘录模式。
还有一点值得指出的是,在下面的代码中,对于客户程序来说“备忘录管理者角色”是不可见的,这样简化了客户程序使用备忘录模式的难度。下面采用“备忘发起角色”来调用访问“备忘录管理者角色”,也可以参考门面模式在客户程序与备忘录角色之间添加一个门面角色。
class Originator{
//这个是要保存的状态
private int state= 90;
//保持一个“备忘录管理者角色”的对象
private Caretaker c = new Caretaker();
//读取备忘录角色以恢复以前的状态
public void setMemento(){
Memento memento = (Memento)c.getMemento();
state = memento.getState();
System.out.println("the state is "+state+" now");
}
//创建一个备忘录角色,并将当前状态属性存入,托给“备忘录管理者角色”存放。
public void createMemento(){
c.saveMemento(new Memento(state));
}
//this is other business methods...
//they maybe modify the attribute state
public void modifyState4Test(int m){
state = m;
System.out.println("the state is "+state+" now");
}
//作为私有内部类的备忘录角色,它实现了窄接口,可以看到在第二种方法中宽接口已经不再需要
//注意:里面的属性和方法都是私有的
private class Memento implements MementoIF{
private int state ;
private Memento(int state){
this.state = state ;
}
private int getState(){
return state;
}
}
}
//测试代码——客户程序
public class TestInnerClass{
public static void main(String[] args){
Originator o = new Originator();
o.createMemento();
o.modifyState4Test(80);
o.setMemento();
}
}
//窄接口
interface MementoIF{}
//“备忘录管理者角色”
class Caretaker{
private MementoIF m ;
public void saveMemento(MementoIF m){
this.m = m;
}
public MementoIF getMemento(){
return m;
}
}
第三种方式是不太推荐使用的:使用clone方法来简化备忘录模式。由于Java提供了clone机制,这使得复制一个对象变得轻松起来。使用了clone机制的备忘录模式,备忘录角色基本可以省略了,而且可以很好的保持对象的封装。但是在为你的类实现clone方法时要慎重啊。
在上面的教学代码中,我们简单的模拟了备忘录模式的整个流程。在实际应用中,我们往往需要保存大量“备忘发起角色”的历史状态。这时就要对我们的“备忘录管理者角色”进行改造,最简单的方式就是采用容器来按照顺序存放备忘录角色。这样就可以很好的实现undo、redo功能了。
四、适用情况
从上面的讨论可以看出,使用了备忘录模式来实现保存对象的历史状态可以有效地保持封装边界。使用备忘录可以避免暴露一些只应由“备忘发起角色”管理却又必须存储在“备忘发起角色”之外的信息。把“备忘发起角色”内部信息对其他对象屏蔽起来, 从而保持了封装边界。
但是如果备份的“备忘发起角色”存在大量的信息或者创建、恢复操作非常频繁,则可能造成很大的开销。
GOF在《设计模式》中总结了使用备忘录模式的前提:
1) 必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能恢复到先前的状态。
2) 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
五、总结
介绍了怎样来使用备忘录模式实现存储对象历史状态的功能,并对基于Java的实现进行了讨论。欢迎大家指正。
