备忘录模式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的对象,相当于原发器对象了,它的变化比较多,大致有如下变化:
具体的示例代码如下:
/**
?* 模拟运行流程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
?????? 好好体会一下上面的示例,由于备忘录对象是一个私有的内部类,外面只能通过备忘录对象的窄接口来获取备忘录对象,而这个接口没有任何方法,仅仅起到了一个标识对象类型的作用,从而保证内部的数据不会被外部获取或是操作,保证了原发器对象的封装性,也就不再暴露原发器对象的内部结构了。