五十八:备忘录模式

备忘录模式又叫做快照模式。备忘录对象是一个用来存储另外一个对象内部状态的快照的对象,备忘录模式的用意是

在不破坏封装的条件下,将一个对象的状态捕捉住,并外部化存储起来,从而可以在将来合适的时候把这个对象还原

到存储起来的状态.备忘录模式常常和命令模式,迭代子模式一起使用

一:备忘录模式所涉及的角色及其责任
(A)备忘录角色,它有如下责任:
(a)将发起人对象的内部状态存储起来
(b)备忘录可以保护其内容不被发起人对象之外的任何对象所读取,它有两个等效接口:
1.窄接口:负责人Cretaker对象看到的是备忘录的窄接口,这个窄接口只允许它把备忘录对象传给

其他对象
2.宽接口:与负责人对象看到的窄接口相反的是,发起人对象可以看到一个宽接口,这个宽接口允

许它读取所有的数据,以便根据这些数据恢复这个发起人对象的内部状态.
(B)发起人(Originator)角色,它有如下责任:
(a)创建一个含有当前的内部状态的备忘录对象
(b)使用备忘录对象存储其内部状态
(C)负责人(Caretaker)角色,它有如下责任:
(a)负责保存备忘录对象
(b)检查备忘录对象的内容

二:备忘录模式的白箱实现---宽接口和白箱
在java语言中实现备忘录模式时,实现"宽"和"窄"两个接口并不是容易的事,如果暂时忽略两个接口的区别,仅仅为

备忘录提供一个宽接口,备忘录角色的内部所存储的状态就对所有对象公开,因此这个实现又叫做"白箱实现",白箱

实现将发起人角色的状态存储在一个大家都看得到的地方,因此破坏了封装性,但是通过程序员的自律,同样可以在

一定程序上实现模式的大部分用意.
package cai.milenfan.basic.test; 
//发起人角色:利用一个新建的备忘录对象把自己的内部状态存储起来
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);
}
}



package cai.milenfan.basic.test; 
//备忘录角色
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;
}
}


package cai.milenfan.basic.test; 
//负责人角色:保存备忘录对象,但是从不修改或查看备忘录对象的内容
public class Caretaker {
private Memento memento;
public Memento retrieveMemento() {
return this.memento;
}
public void saveMemento(Memento memento) {
this.memento = memento;
}
}


package cai.milenfan.basic.test; 

import java.util.Calendar;
/*下面程序中:首先将发起人对象的状态设置成on,并且创建一个备忘录对象将这个状态存储起来,然后将发起人对

象的状态
*改成off,最后又将发起人对象恢复对备忘录对象所存储起来的状态即on状态
*/
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());
System.out.println("Current state = " o.getState());
}
}

二:双重接口及其在Java语言中的实现--宽接口和窄接口
上面的例子破坏了封装,一个系统可能需要将某个对象的状态,比如一个可视构件对象的位置,颜色,大小等状态参

数全部或部分地存储起来,以便将来可以恢复到此状态,通常情况下,一个对象不应当将自己的内部状态暴露给外界

,这就是说如果此对象提供了一些公开的接口以提供其内部状态的话,会产生外部代码直接利用这些接口个性此对象

的危险。备忘录模式利用宽接口和窄接口的设计解决了这个问题.
所谓双重接口,就是对某一个对象提供宽接口,而对另一些对象提供窄接口,根据编程语言的不同,双重要接口的实

现方式有所不同。下面讲宽接口和窄接口在Java中的实现:
(a)两个接口
在这里我们会准备两个接口,一个叫做Wide,一个叫做Narrow,Wide有两个方法,分别是operation1和

operation2,而Narrow则是一个窄到不能再窄的接口:它根本没有声明任何方法.
(b)一个具体类的双重接口
创建一个类ConcreteClass,这个类同时实现Wide和Narrow接口
(c)对不同的对象提供不同的接口
现在必须考虑的核心问题来了,具体类ConcreteClass必须向所有的对象提供Narrow接口,而对一个特殊的

对象提供Wide接口,假设有一个User类和一个Other类,Other类代表所有可能的客户端,而User类代表那个特殊的类

型.那么怎么阻止Other引用到宽接口呢? ?如果我们把宽接口(ConcreteClass)设计成User类的内部成员类可不可以

呢?这时ConcreteClass虽然是User的内部成员类,但是它仍然不仅仅对User类存在,而且对所有的同一个package中

的类都存在,换言之,成员类并没有私有访问权限,但是,如果将ConcreteClass的所有方法都设计成私有的不就达

到目的了吗?是的,这就是所要寻找的解决方案! !请看代码(Wide接口已省略,由ConcreteClass代替):
package cai.milenfan.basic.test; 
public interface Narrow {
}


package cai.milenfan.basic.test; 

public class ConcreteClass implements Narrow {
public void operation1() {
System.out.println("operation1()");
}
public void operation2() {
System.out.println("operation2()");
}
}


package cai.milenfan.basic.test; 

public class User {
class ConcreteClass implements Narrow {
private void operation1() {
System.out.println("operation1()");
}
private void operation2() {
System.out.println("operation2()");
}
}
public Narrow getConcreteClass() {
return (Narrow) new ConcreteClass();
}
}


package cai.milenfan.basic.test; 

public class Other {
public static void main(String[] args) {
User user = new User();
Narrow narrow = user.getConcreteClass();
User.ConcreteClass wide = (User.ConcreteClass) narrow;
}
}

从User类可以看出:只有User对象可以得到并向外界提供ConcreteClass的实例。 User对象可以调用ConcreteClass对

象的私有方法,因为ConcreteClass是User的内部类。当Other对象收到这个实例时,可以根据Narrow类型收到,但是

这个Narrow接口里并没有提供任何方法,也可以根据ConcreteClass类型收到,但是ConcreteClass类型只有私有方法

,因此Other对象无法得到ConcreteClass对象的内部信息.这就是Java语言中的双重接口的设计方案.

三:备忘录模式的黑箱实现
对于Java语言而言,可以将Memento设计成Originator类的内部类,从而将Memento对象封装在Originator里面,在外

部提供一个标识接口MementoIF给Cretaker以及其他对象,这样,Originator类看到的是Memento的所有接口,而

Caretaker以及其他对象看到的仅仅是MementoIF暴露出来的接口.
黑箱设计是这样实现的:Originator类有一个保护成员类Memmento,这个Memmento类实现了MementoIF接口,由于

Memmento类是仅仅对Originator类才存在的,所有的其他对象都可以得到Memmento类的实例,但只能得到它的

MementoIF接口,这就巧妙地实现了双重接口,下面请看源代码:
package cai.milenfan.basic.test; 
public interface MementoIF {

}


package cai.milenfan.basic.test; 

//备忘录角色
public class Memento {
private String savedState;
private Originator lnkOriginator;
private Memento(String someState) {
savedState = someState;
}
private void setState(String someState) {
savedState = someState;
}
private String getState() {
return savedState;
}
}

package cai.milenfan.basic.test;
public class Caretaker {
private MementoIF memento;
public MementoIF retrieveMemento() {
return this.memento;
}
public void saveMemento(MementoIF memento) {
this.memento = memento;
}
}


package cai.milenfan.basic.test; 

//发起人角色:利用一个新建的备忘录对象把自己的内部状态存储起来
public class Originator {
public Originator() {
}
private String state;
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;
}
}
}


package cai.milenfan.basic.test; 

import java.util.Calendar;

/*下面程序中:首先将发起人对象的状态设置成on,并且创建一个备忘录对象将这个状态存储起来,然后将发起人对

象的状态
*改成off,最后又将发起人对象恢复对备忘录对象所存储起来的状态即on状态
*/
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());
}
}

在Originator类中定义了一个内部的Memento类,因此Memento类的全部接口都是私有的,因此只有它自己和发起人

Originator类可以调用。

四:备忘录模式与多重检查点
前面所给出的白箱和黑箱的示意性实现都是只存储一个状态的简单实现,也可以叫做只有一个检查点。常见的软件系

统往往需要存储不止一个状态,而是需要存储多个状态,或者叫做多个检查点.发起人角色(Originator)的状态是存

在于一个Vector对象中的,每个状态都有一个指数index,叫做检查点指数,此外还提供了printStates方法.
package cai.milenfan.basic.test; 

import java.util.Vector;

//备忘录角色
public class Memento {
private Vector states;
private int index;
public Memento(Vector states, int index) {
//备忘录的构造子克隆了传入的states,然后将克隆存入到备忘录对象的内部,这是一个重要的细节,加为

不这样的话,将会造成客户端和备忘录对象持有同一个Vector对象的引用,也可以同时修改这个Vector对象,会造成

系统崩溃。
this.states = (Vector) states.clone();
this.index = index;
}
Vector getStates() {
return states;
}
int getIndex() {
return this.index;
}
}


package cai.milenfan.basic.test; 

import java.util.Enumeration;
import java.util.Vector;

//发起人角色:利用一个新建的备忘录对象把自己的内部状态存储起来
public class Originator {
private Vector states;
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());
}
}
}


package cai.milenfan.basic.test; 

import java.util.Vector;

public class Caretaker {
private Originator o;
private Vector mementos = new Vector();
private int current;
private Memento lnkMemento;
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);
}
}


package cai.milenfan.basic.test; 

import java.util.Calendar;

/*下面程序中:首先将发起人对象的状态设置成on,并且创建一个备忘录对象将这个状态存储起来,然后将发起人对

象的状态
*改成off,最后又将发起人对象恢复对备忘录对象所存储起来的状态即on状态
*/
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();

System.out.println("Restoring to 2");
c.restoreMemento(2);
o.printStates();

System.out.println("Restoring to 0");
c.restoreMemento(0);
o.printStates();

System.out.println("Restoring to 3");
c.restoreMemento(3);
o.printStates();

}
}

五:备忘录模式的应用
(1)J2EE框架中备忘录模式的应用
J2EE框架中的Servlet技术涉及到几个模式,包括模板方法模式,观察者模式等。 Java Servlet引擎提供Session对象

,可以弥补Http协议无状态缺点,存储用户状态.(附:HTTP最初的设计意图是在www上发布文档和图像,因此,它使用

了一个相当简单的通信模型,客户端对文档进行请求,服务器响应以文档或错误代码,最终完成事务处理,服务器不

会保留请求的任何信息,下一次客户端进行请求时,服务器没有方法可以将它与其他客户端区别开来,这就是HTTP的

协议无状态性。基于这种请求/响应的无状态的HTTP协议在很多WEB应用中无法满足实际应用的需求)
(a)什么是Session对象
由于HTTP协议是无状态的,而任何一个有意义的动态信息系统都需要在一定程序上保持用户的历史状态,因此Web技术

发展出了三种不同的办法来解决状态问题:Cookie,URL和HTML的隐藏表Hidden Form等技术。 Session对象是建立在这
些技术上的,但是将实现的细节隐藏起来.当一个新的网络用户调用一个jsp或者servlet时,servlet引擎会创建一个

对象这个用户的Session对象,具体的技术细节可能是向客户端浏览器发送一个含有Session ID的Cookie,或者使用

URL改写技术(默认分类中的"什么是URL重写(改写)技术"一文中有提到)将Session ID包含在URL中.在一段有效的时间

内,同一个浏览器反复访问服务器,而Servlet引擎便可以使用这个Session ID来判断访问者与哪一个Session对象对

应.
(b)操作Session对象的API
每一个Session对象都提供一套put和get方法,可以向Session对象存储信息,或者从中读取信息:
HttpSession currSession = request.getSession(flag);//得到session
currentSession.putValue("userName",Name);//将Name对象加入到session
String name = currSession.getValue("userName");//从session中得到name
显然Session对象就可以当做负责人角色使用,存入Session对象的任何信息都是备忘录角色,网站系统则是发起人角

色.
(2)浏览器的Cookie文件
Cookie是由网络服务器了送出来以存储在网络浏览器上的小量信息,从而下次用户有使用这个浏览器回到这个网络服

务器时,网络服务器可以从此浏览器读回此信息。 Cookie是一个存储在浏览器目录中的文本文件,当浏览器运行时,

存储在RAM(随机存储器)中,一旦用户从该网站或网络服务器中退出,Cookie也可以存储在计算机硬盘上.Cookie实际

上就备忘录角色。浏览器扮演负责人角色,而网站则是起人角色.
(3)系统配置文件
一个软件系统有一些配置参数可能会存储在一个配置文件里,包括一些用户偏爱的视窗位置,系统选项等。比如在视

窗关闭时,软件系统会将视窗位置存储在配置文件中,在软件系统重新启动时,从配置文件中读取这些参数,并将视

窗恢复到上一次关闭时的状态。这就是备忘录模式的应用.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值