本文共 6369 字,大约阅读时间需要 21 分钟。
备忘录模式(Memento) 属于对象的
行为模式。
备忘录模式是我觉得最容易理解的一种模式,它的名字取得非常的贴切。
1. 定义 在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
2. 使用的原因 想要恢复对象某时的原有状态.
memento是一个保存另外一个对象内部状态拷贝的对象.这样以后就可以将该对象恢复到原先保存的状态.
3. 适用的情况举例 有很多备忘录模式的应用,只是我们已经见过,却没细想这是备忘录模式的使用罢了,略略举几例:
eg1. 备忘录在jsp+javabean的使用:
在一系统中新增帐户时,在表单中需要填写用户名、密码、联系电话、地址等信息,如果有些字段没有填写或填写错误,当用户点击“提交”按钮时,需要在新增页 面上保存用户输入的选项,并提示出错的选项。这就是利用JavaBean的scope="request"或scope="session"特性实现的, 即是用备忘录模式实现的。
eg2. 修理汽车的刹车时。首先移开两边的挡板,露出左右刹车片。只能卸下一片,这时另一片作为一个备忘录来表明刹车是怎样安装的。在这片修理完成后,才可以卸下另一片。当第二片卸下时,第一片就成了备忘录。
eg3. 都说人生没有后悔药可买,我们都在为所做的事付出着代价,但在软世界里却有“后悔药”,我改变了某东西的某些状态之后,只要我们之前保存了该东西的某状 态,我们就可以通过备忘录模式实现该东西的状态还原,其实这何尝不是一个能使时光倒流的“月光宝盒”,总“神奇”一词了得。
4. 类图结构及说明 1)类图如下所示: 2)类说明 i)Memento:备忘录角色, 主要负责的工作如下:
将发起人对象的内部状态存储起来;
可以保护其内容不被发起人(Originator)对象之外的任何对象所读取。
ii)Originator:发起人角色,主要完成如下工作:
创建一个含有当前的内部状态的备忘录对象;
使用备忘录对象存储其内部状态。
iii)Caretaker:负责人角色,完成工作如下:
负责保存备忘录对象;
不保存备忘录对象的内容。
5. 使用举例 1)需求 Amigo的mother对Amigo当前的状态很不满意:不爱打扮,做事拖拖拉拉,十足马大哈。该mother想对Amigo进行改造,但又唯恐改造后还不如从前的Amigo好,有可能需要实现“时光倒流”功能。
2)分析 经过细细分析,这个状态保存功能其实可以通过备忘录模式来得到很好的实现,可通过备忘录来保存Amigo当前的状态:不爱打扮,做事拖拖拉拉,十足马大 哈,以及其它的N多优点(哈哈哈。。。),如果改造后的Amigo不如从前,那我们让我们的“月光宝盒”来实现一下“时光倒流”,真神奇啊。。。
3)代码参考实现 i)发起人角色
public class Originator {
private String state;
/**
* 工厂方法,返回一个新的备忘录对象
*/
public Memento createMemento {
return new Memento(state);
}
/**
* 将发起人恢复到备忘录对象所记载的状态
*/
public void restoreMemento(Memento memento) {
this.state = memento.getState();
}
//省略状态state的getter和setter方法
}
ii)备忘录角色
public class Memento {
private String state;
/**
* 构造函数
*/
public Memento(String state) {
this.state = state;
}
//省略state的getter和setter方法
}
iii)负责人角色
public class Caretaker {
private Memento memento;
/**
* 备忘录的取值方法
*/
public Memento retrieveMemento {
return this.memento;
}
/**
* 备忘录的保存方法
*/
public void saveMemento(Memento memento) {
this.memento = memento;
}
}
iv)客户端模拟实现
public class Client {
private static Originator ori = new Originator();
private static Caretaker taker = new Caretaker();
public static void main(String[] args) {
//amigo当前的状态
ori.setState("不爱打扮,做事拖拖拉拉,十足马大哈");
//保存amigo当前的状态
taker.saveMemento(ori.createMemento());
//mother要对amigo进行改造
ori.setState("穿着时髦,做事精练");
//mother发现改造后产生了很多副作用,于是反悔了,要恢复女儿以前的样子
ori.restoreMemento(taker.retrieveMemento());
//amigo被打回原型,^_^
System.out.println("amigo当前情况: " + ori.getState());
}
}
五、Memento模式的特点:
在需要提供保存、恢复对象状态的类中,必须提供两个方法:
.保存对象当前状态方法:将对象自身(this)作为参数传入,创建备忘录。
.恢复对象之前状态的方法:取出备忘录/接收一个备忘录对象,从中获取对象之前的状态
模式的缺点是耗费大,如果内部状态很多,再保存一份,无意要浪费大量内存.
注意:Memento模式保存的是操作前对象的状态,而不是操作后对象的状态;否则就没办法做恢复了.
另一篇:转自http://blog.csdn.net/joyney/archive/2009/04/07/4054594.aspx
一、引子 俗话说:世上难买后悔药。所以凡事讲究个“三思而后行”,但总常见有人做“痛心疾首”状:当初我要是……。如果真的有《大话西游》中能时光倒流的“月光宝盒”,那这世上也许会少一些伤感与后悔——当然这只能是痴人说梦了。
但是在我们手指下的程序世界里,却有的后悔药买。今天我们要讲的备忘录模式便是程序世界里的“月光宝盒”。
二、定义与结构 备忘录(Memento)模式又称标记(Token)模式。GOF给备忘录模式的定义为:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
在讲命令模式的时候,我们曾经提到利用中间的命令角色可以实现undo、redo的功能。从定义可以看出备忘录模式是专门来存放对象历史状态的,这对于很好的实现undo、redo功能有很大的帮助。所以在命令模式中undo、redo功能可以配合备忘录模式来实现。
其实单就实现保存一个对象在某一时刻的状态的功能,还是很简单的——将对象中要保存的属性放到一个专门管理备份的对象中,需要的时候则调用约定好的方法 将备份的属性放回到原来的对象中去。但是你要好好看看为了能让你的备份对象访问到原对象中的属性,是否意味着你就要全部公开或者包内公开对象原本私有的属 性呢?如果你的做法已经破坏了封装,那么就要考虑重构一下了。
备忘录模式只是GOF对“恢复对象某时的原有状态”这一问题提出的通用方案。因此在如何保持封装性上——由于受到语言特性等因素的影响,备忘录模式并没有详细描述,只是基于C++阐述了思路。那么基于Java的应用应该怎样来保持封装呢?我们将在实现一节里面讨论。
来看下“月光宝盒”备忘录模式的组成部分:
1) 备忘录(Memento)角色:备忘录角色存储“备忘发起角色”的内部状态。“备忘发起角色”根据需要决定备忘录角色存储“备忘发起角色”的哪些内部状 态。为了防止“备忘发起角色”以外的其他对象访问备忘录。备忘录实际上有两个接口,“备忘录管理者角色”只能看到备忘录提供的窄接口——对于备忘录角色中 存放的属性是不可见的。“备忘发起角色”则能够看到一个宽接口——能够得到自己放入备忘录角色中属性。
2) 备忘发起(Originator)角色:“备忘发起角色”创建一个备忘录,用以记录当前时刻它的内部状态。在需要时使用备忘录恢复内部状态。
3) 备忘录管理者(Caretaker)角色:负责保存好备忘录。不能对备忘录的内容进行操作或检查。
备忘录模式的类图真是再简单不过了:
三、举例 按照定义中的要求,备忘录角色要保持完整的封装。最好的情况便是:备忘录角 色只应该暴露操作内部存储属性的的接口给“备忘发起角色”。而对于其他角色则是不可见的。GOF在书中以C++为例进行了探讨。但是在Java中没有提供 类似于C++中友元的概念。在Java中怎样才能保持备忘录角色的封装呢?
下面对三种在Java中可保存封装的方法进行探讨。
第一种就是采用两个不同的接口类来限制访问权限。这两个接口类中,一个提供比较完备的操作状态的方法,我们称它为宽接口;而另一个则可以只是一个标示, 我们称它为窄接口。备忘录角色要实现这两个接口类。这样对于“备忘发起角色”采用宽接口进行访问,而对于其他的角色或者对象则采用窄接口进行访问。
这种实现比较简单,但是需要人为的进行规范约束——而这往往是没有力度的。
第二种方法便很好的解决了第一种的缺陷:采用内部类来控制访问权限。将备忘录角色作为“备忘发起角色”的一个私有内部类。好处我不详细解释了,看看代码吧就明白了。下面的代码是一个完整的备忘录模式的教学程序。它便采用了第二种方法来实现备忘录模式。
还有一点值得指出的是,在下面的代码中,对于客户程序来说“备忘录管理者角色”是不可见的,这样简化了客户程序使用备忘录模式的难度。下面采用“备忘发起角色”来调用访问“备忘录管理者角色”,也可以参考门面模式在客户程序与备忘录角色之间添加一个门面角色。
这个例子是我从网上找到的,我觉得它比较形象,就拿过来直接用了。下面是这个例子的代码:
class WindowsSystem{
private String state;
public Memento createMemento(){ //创建系统备份
return new Memento(state);
}
public void restoreMemento(Memento m){ //恢复系统
this.state=m.getState();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
System.out.println("当前系统处于"+this.state);
}
}
class Memento{
private String state;
public Memento(String state) {
this.state = state;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
}
class User{
private Memento memento;
public Memento retrieveMemento() { //恢复系统
return this.memento;
}
public void saveMemento(Memento memento){ //保存系统
this.memento=memento;
}
}
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());
}
}
在本例中,WindowsSystem是发起人角色(Orignation),Memento是备忘录角色(Memento),User 是备忘录管理角色(Caretaker)。Memento提供了两个接口(注意这里的接口,并不是java中的接口,它指的是可被外界调用的方法):一个 是为WindowsSystem 类的宽接口,能够得到WindowsSystem放入Memento的state属性,代码见WindowsSystem的createMemento方 法和restoreMemento方法,createMemento方法向Memento放入state属性,restoreMemento方法获得放入 的state属性。另一个是为User类提供的窄接口,只能管理Memento而不能对它的内容进行任何操作(见User类)。
第三种方式是不太推荐使用的:使用clone方法来简化备忘录模式。由于Java提供了clone机制,这使得复制一个对象变得轻松起来。使用了 clone机制的备忘录模式,备忘录角色基本可以省略了,而且可以很好的保持对象的封装。但是在为你的类实现clone方法时要慎重啊。
在上面的教学代码中,我们简单的模拟了备忘录模式的整个流程。在实际应用中,我们往往需要保存大量“备忘发起角色”的历史状态。这时就要对我们的“备忘 录管理者角色”进行改造,最简单的方式就是采用容器来按照顺序存放备忘录角色。这样就可以很好的实现undo、redo功能了。
四、适用情况 从上面的讨论可以看出,使用了备忘录模式来实现保存对象的历史状态可以有效地保持封装边界。使用备忘录可以避免暴露一些只应由“备忘发起角色”管理却又必须存储在“备忘发起角色”之外的信息。把“备忘发起角色”内部信息对其他对象屏蔽起来, 从而保持了封装边界。
但是如果备份的“备忘发起角色”存在大量的信息或者创建、恢复操作非常频繁,则可能造成很大的开销。
GOF在《设计模式》中总结了使用备忘录模式的前提:
1) 必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能恢复到先前的状态。
2) 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。