备忘录模式2
19.2 解决方案
19.2.1 备忘录模式来解决
来解决上述问题的一个合理的解决方案就是备忘录模式。那么什么是备忘录模式呢?
(1)备忘录模式定义
一个备忘录是一个对象,它存储另一个对象在某个瞬间的内部状态,后者被称为备忘录的原发器。
(2)应用备忘录模式来解决的思路
仔细分析上面的示例功能,需要在运行期间捕获模拟流程运行的对象的内部状态,这些需要捕获的内部状态就是它运行第一个阶段产生的内部数据,并且在该对象之外来保存这些状态,因为在后面它有不同的运行方案。但是这些不同的运行方案需要的初始数据是一样的,都是流程在第一个阶段运行所产生的数据,这就要求运行每个方案后半部分前,要把该对象的状态恢复回到第一个阶段运行结束时候的状态。
在这个示例中出现的、需要解决的问题就是:如何能够在不破坏对象的封装性的前提下,来保存和恢复对象的状态。
看起来跟备忘录模式要解决的问题是如此的贴切,简直备忘录模式像是专为这个应用打造的一样。那么使用备忘录模式如何来解决这个问题呢?
备忘录模式引入一个存储状态的备忘录对象,为了让外部无法访问这个对象的值,一般把这个对象实现成为需要保存数据的对象的内部类,通常还是私有的,这样一来,除了这个需要保存数据的对象,外部无法访问到这个备忘录对象的数据,这就保证了对象的封装性不被破坏。
但是这个备忘录对象需要存储在外部,为了避免让外部访问到这个对象内部的数据,备忘录模式引入了一个备忘录对象的窄接口,这个接口一般是空的,什么方法都没有,这样外部存储的地方,只是知道存储了一些备忘录接口的对象,但是由于接口是空的,它们无法通过接口去访问备忘录对象内的数据。
19.2.2 模式结构和说明
备忘录模式结构如图19.1所示:
图19.1 备忘录模式结构示意图
Memento:
备忘录。主要用来存储原发器对象的内部状态,但是具体需要存储哪些数据是由原发器对象来决定的。另外备忘录应该只能由原发器对象来访问它内部的数据,原发器外部的对象不应该能访问到备忘录对象的内部数据。
Originator:
原发器。使用备忘录来保存某个时刻原发器自身的状态,也可以使用备忘录来恢复内部状态。
Caretaker:
备忘录管理者,或者称为备忘录负责人。主要负责保存备忘录对象,但是不能对备忘录对象的内容进行操作或检查。
19.2.3 备忘录模式示例代码
(1)先看看备忘录对象的窄接口,就是那个Memento接口,这个实现最简单,是个空的接口,没有任何方法定义,示例代码如下:
/** * 备忘录的窄接口,没有任何方法定义 */ public interface Memento { // } |
(2)看看原发器对象,它里面会有备忘录对象的实现,因为真正的备忘录对象当作原发器对象的一个私有内部类来实现了。示例代码如下:
/** * 原发器对象 */ public class Originator { /** * 示意,表示原发器的状态 */ private String state = ""; /** * 创建保存原发器对象的状态的备忘录对象 * @return 创建好的备忘录对象 */ public Memento createMemento() { return new MementoImpl(state); } /** * 重新设置原发器对象的状态,让其回到备忘录对象记录的状态 * @param memento 记录有原发器状态的备忘录对象 */ public void setMemento(Memento memento) { MementoImpl mementoImpl = (MementoImpl)memento; this.state = mementoImpl.getState(); } /** * 真正的备忘录对象,实现备忘录窄接口 * 实现成私有的内部类,不让外部访问 */ private static class MementoImpl implements Memento{ /** * 示意,表示需要保存的状态 */ private String state = ""; public MementoImpl(String state){ this.state = state; } public String getState() { return state; } } } |
(3)接下来看看备忘录管理者对象,示例代码如下:
/** * 负责保存备忘录的对象 */ public class Caretaker{ /** * 记录被保存的备忘录对象 */ private Memento memento = null; /** * 保存备忘录对象 * @param memento 被保存的备忘录对象 */ public void saveMemento(Memento memento){ this.memento = memento; } /** * 获取被保存的备忘录对象 * @return 被保存的备忘录对象 */ public Memento retriveMemento(){ return this.memento; } } |
19.2.4 使用备忘录模式重写示例
学习了备忘录模式的基本知识过后,来尝试一下,使用备忘录模式把前面的示例重写一下,好看看如何使用备忘录模式。
- 首先,那个模拟流程运行的对象,就相当于备忘录模式中的原发器;
- 而它要保存的数据,原来是零散的,现在做一个备忘录对象来存储这些数据,并且把这个备忘录对象实现成为内部类;
- 当然为了保存这个备忘录对象,还是需要提供管理者对象的;
- 为了和管理者对象交互,管理者需要知道保存对象的类型,那就提供一个备忘录对象的窄接口来供管理者使用,相当于标识了类型。
此时程序的结构如图19.2所示:
图19.2 使用备忘录模式重写示例的结构示意图
(1)先来看看备忘录对象的窄接口吧,示例代码如下:
/** * 模拟运行流程A的对象的备忘录接口,是个窄接口 */ public interface FlowAMockMemento { //空的 } |
(2)再来看看新的模拟运行流程A的对象,相当于原发器对象了,它的变化比较多,大致有如下变化:
- 首先这个对象原来暴露出去的内部状态,不用再暴露出去了,也就是内部状态不用再对外提供getter/setter方法了
- 在这个对象里面提供一个私有的备忘录对象,里面封装想要保存的内部状态,同时让这个备忘录对象实现备忘录对象的窄接口
- 在这个对象里面提供创建备忘录对象,和根据备忘录对象恢复内部状态的方法
具体的示例代码如下:
/** * 模拟运行流程A,只是一个示意,代指某个具体流程 */ public class FlowAMock { /** * 流程名称,不需要外部存储的状态数据 */ private String flowName; /** * 示意,代指某个中间结果,需要外部存储的状态数据 */ private int tempResult; /** * 示意,代指某个中间结果,需要外部存储的状态数据 */ private String tempState; /** * 构造方法,传入流程名称 * @param flowName 流程名称 */ public FlowAMock(String flowName){ this.flowName = flowName; } /** * 示意,运行流程的第一个阶段 */ public void runPhaseOne(){ //在这个阶段,可能产生了中间结果,示意一下 tempResult = 3; tempState = "PhaseOne"; } /** * 示意,按照方案一来运行流程后半部分 */ public void schema1(){ //示意,需要使用第一个阶段产生的数据 this.tempState += ",Schema1"; System.out.println(this.tempState + " : now run "+tempResult); this.tempResult += 11; } /** * 示意,按照方案二来运行流程后半部分 */ public void schema2(){ //示意,需要使用第一个阶段产生的数据 this.tempState += ",Schema2"; System.out.println(this.tempState + " : now run "+tempResult); this.tempResult += 22; } /** * 创建保存原发器对象的状态的备忘录对象 * @return 创建好的备忘录对象 */ public FlowAMockMemento createMemento() { return new MementoImpl(this.tempResult,this.tempState); } /** * 重新设置原发器对象的状态,让其回到备忘录对象记录的状态 * @param memento 记录有原发器状态的备忘录对象 */ public void setMemento(FlowAMockMemento memento) { MementoImpl mementoImpl = (MementoImpl)memento; this.tempResult = mementoImpl.getTempResult(); this.tempState = mementoImpl.getTempState(); } /** * 真正的备忘录对象,实现备忘录窄接口 * 实现成私有的内部类,不让外部访问 */ private static class MementoImpl implements FlowAMockMemento{ /** * 示意,保存某个中间结果 */ private int tempResult; /** * 示意,保存某个中间结果 */ private String tempState; public MementoImpl(int tempResult,String tempState){ this.tempResult = tempResult; this.tempState = tempState; } public int getTempResult() { return tempResult; } public String getTempState() { return tempState; } } } |
(3)接下来要来实现提供保存备忘录对象的管理者了,示例代码如下:
/** * 负责保存模拟运行流程A的对象的备忘录对象 */ public class FlowAMementoCareTaker { /** * 记录被保存的备忘录对象 */ private FlowAMockMemento memento = null; /** * 保存备忘录对象 * @param memento 被保存的备忘录对象 */ public void saveMemento(FlowAMockMemento memento){ this.memento = memento; } /** * 获取被保存的备忘录对象 * @return 被保存的备忘录对象 */ public FlowAMockMemento retriveMemento(){ return this.memento; } } |
(4)最后来看看,如何使用上面按照备忘录模式实现的这些对象呢,写个新的客户端来测试一下,示例代码如下:
public class Client { public static void main(String[] args) { // 创建模拟运行流程的对象 FlowAMock mock = new FlowAMock("TestFlow"); //运行流程的第一个阶段 mock.runPhaseOne(); //创建一个管理者 FlowAMementoCareTaker careTaker = new FlowAMementoCareTaker(); //创建此时对象的备忘录对象,并保存到管理者对象那里,后面要用 FlowAMockMemento memento = mock.createMemento(); careTaker.saveMemento(memento);
//按照方案一来运行流程后半部分 mock.schema1();
//从管理者获取备忘录对象,然后设置回去, //让模拟运行流程的对象自己恢复自己的内部状态 mock.setMemento(careTaker.retriveMemento());
//按照方案二来运行流程后半部分 mock.schema2(); } } |
运行结果跟前面的示例是一样的,结果如下:
PhaseOne,Schema1 : now run 3 PhaseOne,Schema2 : now run 3 |
好好体会一下上面的示例,由于备忘录对象是一个私有的内部类,外面只能通过备忘录对象的窄接口来获取备忘录对象,而这个接口没有任何方法,仅仅起到了一个标识对象类型的作用,从而保证内部的数据不会被外部获取或是操作,保证了原发器对象的封装性,也就不再暴露原发器对象的内部结构了。