描述:
对象的状态可以定义为在特定的时间点对象的属性值。备忘录模式(Memento Pattern)应用于保存和跟踪对象的状态,以便于必要的时候可以把对象恢复到以前的状态。它很像恢复操作。备忘录模式(Memento Pattern)可以在不暴露对象的内部结构的情况下完成这样的功能。需要获取以前状态的对象就是指发起者(Originator)。当客户需要保存发起者的状态时,客户需要发起者的当前状态。发起者存贮所有保持它状态的属性到一个独立的对象,这个对象就是备忘录(纪念、记忆)Memento,把备忘录(Memento)对象返回给客户。备忘录(Memento)对象可以看作在给定的时间点包含另一个对象内部状态的对象。备忘录(Memento)对象必须向除了发起者以外的所有对象隐藏发起者变量的值。当发起者答应备忘录(Memento)对象访问它的内部状态时,备忘录(Memento)对象应该被设计为对其他对象采取访问限制的对象。
当客户需要把发起者的状态恢复到以前的状态时,它只是简单的把备忘录(Memento)对象返回给发起者。发起者使用包含在备忘录(Memento)对象中的状态信息,恢复自己到备忘录(Memento)对象中保存的状态。
例子:
数据转化(Data conversion)总是那些涉及到从遗留系统转化到应用新技术的系统不可缺少的一部分。让我们假定一个需要把客户数据从文本文件移植到关系型数据库中的类似应用程序。在将客户数据发送给数据库以前,要对客户纪录进行验证。
现实中,客户纪录需要包括很多属性,但是为了简单,让我们假定每一个客户纪录只有三个属性??first name、last name和credit card number。验证过程也很简单,只要last name不为空而且credit card number(信用卡号)仅有0-9的数字组成。当发现一个无效的客户记录时,验证过程需要停止、提示用户修正数据并重新开始。在这个时间点上,数据转化(Data conversion)过程的状态需要保存在一个备忘录(Memento)对象内部。当用户重新开始验证过程时,数据装化过程从保存在备忘录(Memento)对象中的状态开始,验证过程从它停止的地方恢复,而不是从原数据起点重新开始。通常,备忘录(Memento)对象既可以保存在内存中也可以保存在持久介质上。在这个应用中,当应用被打断以后,状态需要保存,而且当应用再次运行的时候需要恢复。因此,在这种情况下,不适于把备忘录(Memento)对象保存在内存中,而是需要保存在持久介质上。
不是直接把合法的客户纪录插入到关系数据库中,应用程序而是生成一个由SQL插入语句组成的文本文件,执行这些SQL语句可以把数据插入到数据库中。
让我们为这个验证过程设计不同的组件。
DataConverter(发起者)
DataConverter类(图32.1和Listing32.1)是数据转化过程的实现。
Figure 32.1: DataConverter Class?The Originator
Listing 32.1: DataConverter Class
public class DataConverter {
public static final String DATA_FILE = "Data.txt";
public static final String OUTPUT_FILE = "SQL.txt";
PRivate long ID = 0;
public Memento createMemento() {
return (new Memento(ID));
}
public void setMemento(Memento memento) {
if (memento != null)
ID = memento.getID();
}
public long getLastProcessedID() {
return ID;
}
public void setLastProcessedID(long lastID) {
ID = lastID;
}
public boolean process() {
boolean sUCcess = true;
String inputLine = "";
long currID = 0;
try {
File inFile = new File(DATA_FILE);
BufferedReader br = new BufferedReader(
new InputStreamReader(
new FileInputStream(inFile)));
long lastID = getLastProcessedID();
while ((inputLine = br.readLine()) != null) {
StringTokenizer st =
new StringTokenizer(inputLine, ",");
String strID = st.nextToken();
currID = new Long(strID).longValue();
if (lastID < currID) {
Customer c =
new Customer(strID, st.nextToken(),
st.nextToken(), st.nextToken());
if (!(c.isValid())) {
success = false;
break;
}
ID = new Long(strID).longValue();
FileUtil util = new FileUtil();
util.writeToFile(OUTPUT_FILE, c.getSQL(),
true, true);
}
}
br.close();
}//Try
catch (Exception ex) {
System.out.println(" An error has occurred " +
ex.getMessage());
System.exit(1);
}
if (success == false) {
System.out.println("An error has occurred at ID=" +
currID);
System.out.println("Data Record=" + inputLine);
return false;
}
return true;
}
class Memento implements java.io.Serializable {
private long lastProcessedID;
private Memento(long ID) {
lastProcessedID = ID;
}
private long getID() {
return lastProcessedID;
}
}//end of class
}//end of class
ID
实例变量ID组成了DataConverter的状态,它代表了最后一个被成功处理的客户纪录的客户ID。
Memento
Memento定义为DataConverter的一个内部类,Memento将它的构造函数和其他方法定义为私有。
在Java中,一个类可以访问它内部类的私有成员。
DataConverter可以访问这些方法,但是其他的对象不可以访问。因为,当应用结束的时候,DataConverter的状态需要被保存。Memento对象需要被序列化(serialize)到一个文件中。因此,Memento类需要实现java.io.Serializable接口,以表明自己是一个可序列化(Serializable)的类。
在JAVA中,一个序列化的类必须:
使用transient 要害字明确指出不需要序列化的属性。
实现java.io.Serializable接口
可以访问它的第一个非序列化夫类的零参数的构造函数。
process
process方法读取元数据文件,通过Customer helper类验证客户数据。对于每一个有效的客户纪录,相应的SQL插入语句被写入到输出文件中。当碰到无效客户纪录时,数据转化过程停止。
createMemento
如方法名字,这个方法负责创建Memento对象,它把DataConverter对象的当前状态保存到一个Memento实例内,并放回它。
setMemento
取出输入的Memento对象的状态信息,重新设置DataConverter的状态到此状态。
DCClient (Client)
客户DCClient(Listing 32.2)首先初始化DataConverter,调用DataConverter实例的process方法开始数据转化过程。假如process方法在处理原数据文件期间碰到无效的客户数据,它会调用DataConverter实例的createMemento方法捕捉当前状态。createMemento方法返回一个Memento对象。客户DCClient使用MementoHandler对象负责序列化Memento实例到一个文件。
Listing 32.2: DCClient Class
public class DCClient {
public static void main(String[] args) {
MementoHandler objMementoHandler = new MementoHandler();
DataConverter objConverter = new DataConverter();
objConverter.setMemento(objMementoHandler.getMemento());
if (!(objConverter.process())) {
System.out.println("Description: Invalid data - " +
"Process Stopped");
System.out.println("Please correct the Data and " +
"Run the application Again");
objMementoHandler.setMemento(
objConverter.createMemento());
}
}
}
一旦数据被校正,客户DCClient就会再次运行。
客户DCClient调用MementoHandler 上的getMemento 方法请求它保存Memento对象。
MementoHandler 从文件中反序列化以前的Memento对象,并把它放回给客户。
客户把它作为DataConverter 的set-Memento方法的参数传递给DataConverter 。DataConverter使自己返回到保存在Memento对象中的状态。从原来停止的地方恢复数据转化过程。
MementoHandler
The MementoHandler (Listing 32.3) 包含了Memento 对象的一个引用。客户DCClient把一个Memento的实例传递给它。
Listing 32.3: MementoHandler Class
public class MementoHandler {
public static final String ID_FILE = "ID.txt";
private DataConverter.Memento objMemento = null;
public DataConverter.Memento getMemento() {
ObjectInputStream objStream = null;
FileUtil util = new FileUtil();
if (util.isFileExists(ID_FILE)) {
//read the object from the file
try {
objStream = new ObjectInputStream(
new FileInputStream(new File(ID_FILE)));
objMemento = (DataConverter.Memento)
objStream.readObject();
objStream.close();
} catch (Exception e) {
System.out.println("Error Reading Memento");
System.exit(1);
}
//delete the old memento
util.deleteFile(ID_FILE);
}
return objMemento;
}
public void setMemento(DataConverter.Memento memento) {
ObjectOutputStream objStream = null;
//write the object to the file
try {
objStream = new ObjectOutputStream(
new FileOutputStream(new File(ID_FILE)));
objStream.writeObject(memento);
objStream.close();
} catch (Exception e) {
System.out.println("Error Writing Memento");
System.exit(1);
}
}
}//end of class
如上面介绍的,任何时候数据转化过程没有验证完全部的原数据文件,客户要捕捉DataConverter的状态到一个Memento中,并停止应用程序。为了使这个Memento在下次运行的时候有效,它必须被保存到持久介质上,这就涉及到了对象的序列化。假如在下次运行的时候,DataConverter要返回到它原来的状态,这个Memento对象必须被重构,这又涉及到了对象的反序列化。这些细节由MementoHandler类来处理,使得所有客户(DataConverter和Memento对象)免于处理这些细节。
这样也是的改变Memento的存储方式变得很轻易。例如,Memento需要保存到数据库中,而不是文件中时,只要修改MementoHandler就可以了,不需要修改任何客户类的实现。
图32.2显示了在数据转化这个例子中,不同对象之间的关联关系。
Figure 32.2: Data Conversion Application?Class Association
Figure 32.3 shows the application message flow.
Figure 32.3: Application Message Flow
例子2(自己找的)
经常使用计算机的人恐怕对系统备份(Memento)不会生疏,当你的Windows系统运行正常时,对它进行备份,当系统运行有问题时,就可以调用备份快速的将系统恢复,这样就可以大量节省重新装系统的痛苦,非凡是当你缺少某一驱动,或在装系统是出现一些怪问题时,犹为痛苦。我想有过这种经历的人应该很了解吧,呵呵!
好了,下面让我们看看这个过程该如何实现吧:
1、我们先定义Windows系统(WindowsSystem)类:
public class WindowsSystem {
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(当前系统处于+this.state);
}
}
2、再定义备份(Memento)类:
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、定义用户(User)类:
public class User {
private Memento memento;
public Memento retrieveMemento() {//恢复系统
return this.memento;
}
public void saveMemento(Memento memento){//保存系统
this.memento=memento;
}
}
4、编写测试类:
public class Test {
public static void main(String args[]) {
WindowsSystem WinXP = new WindowsSystem(); //Winxp系统
User user = new User(); //某一用户
Winxp.setState(好的状态); //Winxp处于好的运行状态
user.saveMemento(Winxp.createMemento()); //用户对系统进行备份,Winxp系统要产生备份文件
Winxp.setState(坏的状态); //Winxp处于不好的运行状态
Winxp.restoreMemento(user.retrieveMemento()); //用户发恢复命令,系统进行恢复
System.out.println(当前系统处于+Winxp.getState());
}
}
5、说明:
A:定义:Memento对象是一个保存另外一个对象内部状态拷贝的对象,这样以后就可以将该对象恢复到原先保存的状态。
B:Memento模式的用意是在不破坏封装的条件下,将一个对象的状态捕捉住,并外部化,存储起来,从而可以在将来合适的时候把这个对象还原到存储起来的状态。
C:Memento模式所涉及的角色有三个,备忘录角色、发起人角色和负责人角色。
备忘录角色的作用:
(1) 将发起人对象的内部状态存储起来,备忘录可以根据发起人对象的判定来决定存储多少发起人对象的内部状态。
(2) 备忘录可以保护其内容不被发起人对象之外的任何对象所读取。
发起人角色的作用:
(1) 创建一个含有当前内部状态的备忘录对象。
(2) 使用备忘录对象存储其内部状态。
负责人角色的作用:
(1) 负责保存备忘录对象。
(2) 不检查备忘录对象的内容。
D:在本例中,备份(Memento)类是备忘录角色、Windows系统(WindowsSystem)类是发起人角色、用户(User)类是负责人角色。