`

备忘录模式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

       好好体会一下上面的示例,由于备忘录对象是一个私有的内部类,外面只能通过备忘录对象的窄接口来获取备忘录对象,而这个接口没有任何方法,仅仅起到了一个标识对象类型的作用,从而保证内部的数据不会被外部获取或是操作,保证了原发器对象的封装性,也就不再暴露原发器对象的内部结构了。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics