备忘录模式
来源:互联网 发布:此域名正在升级中 编辑:程序博客网 时间:2024/04/30 03:11
备忘录模式(Memento Pattern)
备忘录模式模式又叫快照模式(Snapshot Pattern)或Token模式,是对象的行为模式
备忘录对象是一个用来存储另外一个对象内部状态的快照(snapshot)的对象。备忘录模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉(Capture)住,并外部化(Externalize),存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。备忘录模式常与命令模式和迭代子模式一起使用,
常见的软件系统往往不止存储一个状态,而是要存储多个状态。这些状态常常是一个对象历史发展的不同阶段的快照,存储这些快照的备忘录对象叫此对象的历史;某一个快照所处的位置叫检查点(Check Point)
一、备忘录模式的结构
备忘录模式所涉及的角色有3个:备忘录、发起人、负责人
1、备忘录角色(Memento)
备忘录角色有以下的责任
(1)将发起人对象的内部状态存储起来。备忘录可以根据发起人对象的判断来决定存储多少发起人对象的内部状态
(2)备忘录可以保护其内容不被发起人对象之外的任何对象所读取。备忘录有2个等效的接口
1)窄接口:负责人对象(和其它发起人对象之外的任何对象)看到的是备忘录的窄接口(narrow interface),这个窄接口只允许它把备忘录对象传给其它对象
2)宽接口:发起人对象可以看到一个宽接口(wide interface),这个宽接口允许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态。
2、发起人角色(Originator)角色
1)创建一个含有当前的内部状态的备忘录对象
2)使用备忘录对象存储其内部状态
3、负责人角色(Caretaker)角色
1)负责保存备忘录对象
2)不检查备忘录对象的内容
二、备忘录模式的白箱实现
1、宽接口和白箱
在java语言中实现备忘录模式时,实现“宽”和“窄”接口并不是容易的事。如果暂时忽略2个接口的区别,仅为备忘录角色提供一个宽接口的话,情况就变的简单多了。但是由于备忘录角色对任何对象都提供一个接口,即宽接口。备忘录角色的内部所存储的状态就对所有对象公开。因此这个实现叫白箱实现。
白箱实现将发起人角色的状态存储在一个大家看得到的地方,因此是破坏封装性的。但是通过程序员的自律,同样可以在一定程度上实现模式的大部分用意。因此白箱实现仍然是有意义的。
2、实现
1)发起人角色:利用一个新创建的备忘录将自己的内部状态存储起来。
public class Originator
{
private String state;
//工厂方法,返回一个备忘录对象
public Memento createMemento()
{
return new Memento(state);
}
//将发起人恢复到备忘录对象所记载的状态
public void restoreMemento(Memento memento)
{
this.state = memento.getState();
}
//状态的取值方法
public String getState()
{
return this.state;
}
//状态的赋值方法
public void setState(String state)
{
this.state = state;
System.out.println("Current state = " + this.state);
}
}
2)备忘录角色:将发起人对象传入的状态存储起来。备忘录角色会根据发起人对象来判断将发起人的多少个内部状态存储起来。
public class Memento
{
private String state;
//构造方法
public Memento(String state)
{
this.state = state;
}
状态的取值方法
public String getState()
{
return this.state;
}
//状态的赋值方法
public void setState(String state)
{
this.state = state;
}
}
3)负责人角色:负责保存备忘录对象,但从不修改(甚至不查看)备忘录对象的内容(一个更好的实现是负责人对象根本无法从备忘录对象中读取和修改内容)。
public class Caretaker
{
private Memento memento;
//备忘录的取值方法
public Memento retrieveMemento()
{
return this.memento;
}
//备忘录的赋值方法
public void saveMemento(Memento memento)
{
this.memento = memento;
}
}
4)示意客户端:
public class Client
{
private static Originator o = new Originator();
private static Caretaker c = new Caretaker();
public static void main(String[] args)
{
//改变发起人对象的状态
o.setState("On");
//创建备忘录对象,并将发起人对象的状态保存起来
c.saveMemento( o.createMemento() );
//修改发起人的状态
o.setState("Off");
//恢复发起人的状态
o.restoreMemento( c.retrieveMemento() );
}
}
首先将发起人对象的状态设置成“On”(或任何有效状态),并且创建一个备忘录对象将这个状态保存起来;然后将发起人的状态改为“Off”(或任何有效状态);最后又将发起人对象恢复到备忘录对象所存储起来的状态,即“On”状态(或先前所存储的任何状态)。
3、白箱实现的优缺点
白箱实现的一个明显的好处是比较简单,因此常用做教学目的。明显的缺点是破坏发起人状态的封装。
这个示意性实现只能存储一个状态(又叫一个检查点)。
三、双重接口及其在Java语言中的实现
所谓的双重接口,就是对某一个对象提供宽接口,而对另一些对象提供窄接口。根据编程语言的性能,双重接口的实现方式有所不同。
1、备忘录模式的黑箱实现
对于Java语言而言,可以将备忘录角色(Memento)设置成发起人角色(Originator)的内部类,从而将备忘录对象(Memento)封装在发起人角色(Originator)里;在外部提供一个标识接口(MementoIF)给负责人(Caretaker)以及其它对象。这样,发起人看到的是备忘录的所有接口,而负责人及其它对象看到的仅仅是标识接口。
1)发起人角色:
在Originator类中定义了一个内部的Memento类,由于此Memento类的全部接口都是私有的,因此只有它自己和发起人Originator类可以调用。
public class Originator
{
private String state;
//构造方法
public Originator()
{
}
//工厂方法,返回一个新的备忘录对象
public MementoIF createMemento()
{
return new Memento( this.state );
}
//将发起人恢复到备忘录对象记录的状态
public void restoreMemento( MementoIF memento)
{
Memento aMemento = (Memento) memento;
this.setState( aMemento.getState() );
}
//状态的取值方法
public String getState()
{
return this.state;
}
//状态的赋值方法
public void setState(String state)
{
this.state = state;
System.out.println("state = " + state);
}
//内部成员类,备忘录角色
class Memento implements MementoIF
{
private String savedState;
//构造方法
private Memento(String someState)
{
savedState = someState;
}
//状态的赋值方法
private void setState(String someState)
{
savedState = someState;
}
//状态的取值方法
private String getState()
{
return savedState;
}
}
}
2)窄接口MementoIF:这是个标识接口,它没有定义处任何方法。
public interface MementoIF
{
}
3)负责人角色:负责人能够得到的备忘录对象是以MementoIF为接口的。由于这个接口仅仅是一个标识接口,因此负责人角色不可能改变这个备忘录对象的内容。
public class Caretaker
{
private MementoIF memento;
//备忘录的取值方法
public MementoIF retrieveMemento()
{
return this.memento;
}
//备忘录的赋值方法
public void saveMemento(MementoIF memento)
{
this.memento = memento;
}
}
4)客户端
将发起人对象的状态存入备忘录的时序
(1)将发起人的状态设置为“On”
(2)调用发起人角色的createMemento()方法,创建一个备忘录对象将这个状态存储起来。此时createMemento()方法返回一个明显类型为MementoIF,真实类型为Originator内部的Memento类型的对象
(3)将备忘录对象存储到负责人对象中去。由于负责人对象拿到的仅仅是MementoIF类型,因此无法读出备忘录对象的内部状态。
将发起人对象恢复到备忘录对象所记录的状态的时序
(1)将发起人对象的状态设置为、“Off”
(2)将备忘录对象从负责人对象中取出。注意此时仅得到一个MementoIF接口,因此无法读出此对象的状态。
(3)将发起人对象的状态恢复成备忘录对象所存储的状态。由于发起人对象的内部类Memento实现了MementoIF接口,这个内部类是传入的备忘录对象的真实类型,因此发起人对象可以利用内部类Memento的私有接口读出此对象的内部状态。
public class Client
{
private static Originator o = new Originator();
private static Caretaker c = new Caretaker();
public static void main(String[] args)
{
//改变发起人的状态
o.setState("On");
创建备忘录对象,并将发起人的状态存储起来
c.saveMemento( o.createMemento() );
//修改发起人的状态
o.setState("Off");
//恢复发起人的状态
o.restoreMemento( c.retrieveMemento() );}
}
四、备忘录模式与多重检查点
常见的软件系统往往需要存储不止一个状态,而是需要存储多个状态,或者叫做多个检查点。
备忘录模式可以将发起人对象的状态存储到备忘录对象里,备忘录模式可以将发起人对象恢复到备忘录对象所存储的某一个检查点上。
有些备忘录模式的实现在理论上允许无限个状态。有些实现只允许有限个状态。
1、一个示意性的、有多重检查点的备忘录模式实现
(1)发起人角色:发起人角色的状态存在一个Vector对象里,每个状态都有一个指数index,叫做检查点指数。此外printStates()方法是一个提供辅助功能的方法
public class Originator
{
private Vector states; //定义一个Vector
private int index;
//构造方法
public Originator()
{
states = new Vector();
index = 0;
}
//工厂方法,返回一个备忘录角色
public Memento createMemento()
{
return new Memento(states, index);
}
//将发起人恢复到备忘录记录的状态上
public void restoreMemento(Memento memento)
{
states = memento.getStates();
index = memento.getIndex();
}
//状态的赋值方法
public void setState(String state)
{
this.states.addElement(state);
index++;
}
//辅助方法,将状态打印出来
public void printStates()
{
System.out.println("Total number of states : " + index);
for(Enumeration e = states.elements(); e.hasMoreElements(); )
{
System.out.println( e.nextElement() );
}
}
}
(2)备忘录角色:可以存储任意多的状态,外界可以使用检查点指数index来取出检查点上的状态。备忘录的构造方法克隆传入的状态,然后将克隆存入备忘录对象内部,这是一个重要的细节,因为不这样做的话,将会造成客户端和备忘录对象持有对同一个Vector对象的引用,也可以同时修改Vector对象,会造成系统崩溃。
public class Memento
{
private Vector states; //定义一个Vector
private int index;
//构造方法
public Memento(Vector states, int index)
{
this.states = (Vector) states.clone();
this.index = index;
}
//状态的取值方法
Vector getStates()
{
return states;
}
//得到检查点指数
int getIndex()
{
return this.index;
}
}
(3)负责人角色:可以根据检查点指数index来恢复发起人角色的状态,也可以取消一个检查点。
public class Caretaker
{
private Originator o;
private Vector mementos = new Vector();
private int current;
//构造方法
public Caretaker(Originator o)
{
this.o = o;
current = 0;
}
//创建一个新的检查点
public int createMemento()
{
Memento memento = o.createMemento();
mementos.addElement(memento);
return current++;
}
//将发起人恢复到检查点
public void restoreMemento(int index)
{
Memento memento = (Memento) mementos.elementAt(index);
o.restoreMemento(memento);
}
//将某个检查点删除
public void removeMemento(int index)
{
mementos.removeElementAt(index);
}
}
(4)客户端
public class Client
{
private static Originator o = new Originator();
private static Caretaker c = new Caretaker(o);
static public void main(String[] args)
{
//改变状态
o.setState("state 0");
//建立检查点
c.createMemento();
//改变状态
o.setState("state 1");
//建立检查点
c.createMemento();
//改变状态
o.setState("state 2");
//建立检查点
c.createMemento();
//改变状态
o.setState("state 3");
//建立检查点
c.createMemento();
//改变状态
o.setState("state 4");
//建立检查点
c.createMemento();
//打印所有的状态
o.printStates();
//恢复到第2个检查点
System.out.println("Restoring to 2");
c.restoreMemento(2);
o.printStates();
//恢复到第0个检查点
System.out.println("Restoring to 0");
c.restoreMemento(0);
o.printStates();
//恢复到第3个检查点
System.out.println("Restoring to 3");
c.restoreMemento(3);
o.printStates();
}
}
五、使用备忘录模式的优点和缺点
1、优点
(1)有时一些发起人对象的内部信息必须保存在发起人对象以外的地方,但是必须要由发起人对象自己读取。这时使用备忘录模式可以把复杂的发起人内部信息对其它的对象屏蔽起来,从而可以恰当的保存封装的边界
(2)本模式简化了发起人(Originator)类。发起人不再需要管理和保存其内部状态的一个个版本,客户端可以自行管理它们所需要的这些状态的版本。
(3)当发起人角色的状态改变时,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录模式将状态复原。
2、缺点
(1)如果发起人角色的状态需要完整地存储到备忘录对象中,那么在资源消耗上备忘录对象会很昂贵。
(2)当负责人角色将一个备忘录存储起来,负责人可能并不知道这个状态会占用多大的存储空间,从而无法提醒用户一个操作是否会很昂贵。
(3)当发起人角色的状态发生改变的时候,有可能这个状态无效。