备忘录 (memento、记忆)模式满足系统对撤销/undo、回滚操作的需求。
由于需要返回到变化前,原始对象Originator(原创者、真身)的发生变化的属性如state就必需被记忆。当state发生变化时,创建一个还原点(Restore Point),用一个备忘录/Memento对象记录state的当前状态。而当Originator的客户调用Originator.undo()时恢复到变化前的状态。
4.5.1 过程式实现
如果MyOriginator仅有一个状态int state需要记忆,例如state从100→80→60,然后undo到80→100,这是一个及其简单的工作,不需要太多类型参与,MyOriginator自己搞定。可以容易地给出过程式实现。下面的例程用于说明备忘录模式本身是一个简单的概念。
只有一个状态int state需要记忆时,可以用数组int[] memento记录各次变化前的数据,数组下标标明当前数据。例程4-10过程式备忘录
package property.memento;
import static tool.Print.*;
/**
* @author yqj2065
* @version 2014.9
*/
public class MyOriginator{
private int state= 100;//要记忆的状态。可以有多个
private int times = 0;//第几次
private int[] memento=new int[6];//记忆6次
public void modifyState(int x){
memento[times++] =state;
state = x;
}
public void undo(){
state=memento[--times] ;
}
@Override public String toString(){
return ""+state;
}
public static void testMyOriginator(){
MyOriginator o = new MyOriginator();pln(o);
o.modifyState(80); pln("→"+o);
o.modifyState(60); pln("→"+o);
o.undo(); pln("undo→"+o);
o.undo(); pln("undo→"+o);
}
}
注意:如果一个
引用类型的状态State state需要记忆,而网上的一些例子保存的
不是State 而直接保存State 的各种成员变量,就不要看那些例程了——它们等价于过程式实现。
4.5.2 内部类 Vs. 不变对象
不失一般性,原始对象MyOriginator2有一个状态State state(有成员变量int x,y)需要记忆,每个备忘录/Memento对象记录一次State的快照,一系列的Memento对象交给备忘录的Caretaker/保管员管理。
设计Memento有一个基本要求:它保存的状态数据不得被修改。虽然MyOriginator2的state域不断在变化,但Memento对象封存了state的一个瞬间,因此将它设计为不变类(immutable class)。
如果备忘录单纯地满足系统对撤销/undo、回滚操作的需求,不考虑Originator的封装性,称为 白箱备忘录模式;考虑Originator的封装性,称为 黑箱备忘录模式。
不管白猫黑猫,类图基本上如图所示。外界Test仅仅依赖于原始对象Originator,Originator提供设置还原点方法setRestorePoint()和回滚方法undo(),其他均被隐藏。GoF中没有说明Originator的用户角色,而Originator的接口值得商榷:外界需要工厂方法createMemento()吗?难道外界设置还原点时一定要指定Memento对象作为参数吗?在上面的代码中setRestorePoint()不需要指定Memento,因此工厂方法createMemento()也就不作为接口。
显然,Memento被保存的状态数据是不得被修改的。另一方面,Memento必须提供方法
State getState()
以便原始对象Originator获得State以前的某个状态从而实现反悔的目的。
如何在提供getState()方法的同时保证被保存的状态数据不被修改呢?有下列手段。①限制Originator之外的对象访问Memento.getState()方法。
如果Originator没有公开接口getState(),Memento提供的getState()方法是不负责任的——破坏封装性。为了保护Originator的封装性,可以使Memento对外界对象不可见,例如将Memento设计成Originator的内部私有类。最少也要将Memento.getState()方法的访问权限改为包级私有。[设计模式·5.6]中给备忘录模式的定义:『在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态』。 GoF的描述强调Originator的封装性。假如State是某个地点,外界通过访问多个Memento对象收集Originator的出没地,可能会找到规律来给Originator下套。 ② 保证被保存的状态数据不被修改。在很多情况下,Originator公开接口getState(),并不在意外界访问它的State属性,此时备忘录模式 单纯地满足系统对撤销/undo、回滚操作的需求。例程4-11不变类
package property.memento;
public final class Memento1{
private final VirtualState state;//要记忆的状态,
public Memento1(State state){
this.state =new VirtualState(state) ;
}
State getState(){
State state = new State(this.state.x,this.state.y);
return state;
}
/**
* 可以使用深克隆技术。
*/
class VirtualState{
private int x;
private int y ;
public VirtualState(State state){
x = state.getX();
y = state.getY();
}
}
}
注意:Memento1中的VirtualState意在提醒程序员,此时保存的状态不是Originator的成员state同一个对象。真正使用的Memento2对象是不变对象,即一经创建就不可改变的对象。
package property.memento;
<p>public final class Memento2{</p><p> privatefinal State state;//要记忆的状态,</p><p> publicMemento2(final State state){ </p><p> this.state =new State(){{setX(state.getX());setY(state.getY());}};</p><p> }</p><p> StategetState(){</p><p> returnnew State(){{setX(state.getX());setY(state.getY());}};</p><p> }</p>}
例程 7 4 保管员
package property.memento;
public class Caretaker2{
private Memento2[] ms = new Memento2[6];
private int times = 0;//第几次
public Caretaker2(){ }
public void save(Memento2 m){
ms[times++] =m;
}
public Memento2 getMem(){
return ms[--times];
}
}
最后是原始对象MyOriginator2、State和测试代码。
例程 7 5 原始对象
package property.memento;
public class MyOriginator2{
private int i;//不需要记忆的状态
private State state = new State();//要记忆的状态。
Caretaker2 c = new Caretaker2();
public MyOriginator2(int i,int j){
state.setX(i);
state.setY(j);
}
public void modifyState(int i,int j){
c.save(new Memento2(state));
state.setX(i);
state.setY(j);
}
public void undo(){
state=c.getMem().getState();
}
@Override public String toString(){
return ""+state;
}
}
package property.memento;
public class State{
private int x,y ;
public State(){}
public State(int i,int j){x = i;y = j;}
public void setX(int i){x = i;}
public void setY(int i){y = i;}
public int getX(){ return x; }
public int getY(){ return y; }
@Override public String toString(){
return "x="+x+" y="+y;
}
}
public static void testMyOriginator2(){
MyOriginator2 o = new MyOriginator2(8,8); pln(" "+o);
o.modifyState(5,7);pln("→ "+o);
o.modifyState(2,3);pln("→ "+o);
o.undo(); pln("undo→"+o);
o.undo(); pln("undo→"+o);
}
输出:
x=8 y=8
→ x=5 y=7
→ x=2 y=3
undo→x=5 y=7
undo→x=8 y=8
最后编辑时间:2015.4.20