开发仿真系统

   考虑这样一个仿真应用,功能是:模拟运行针对某个具体问题的多个解决方案,记录运行过程的各种数据,在模拟运行完成过后,好对这多个解决方案进行比较和评价,从而选定最优的解决方案。

这种仿真系统,在很多领域都有应用,比如:工作流系统,对同一问题制定多个流程,然后通过仿真运行,最后来确定最优的流程做为解决方案;在工业设计和制造领域,仿真系统的应用就更广泛了。

由于都是解决同一个具体的问题,这多个解决方案并不是完全不一样的,假定它们的前半部分运行是完全一样的,只是在后半部分采用了不同的解决方案,后半部分需要使用前半部分运行所产生的数据。

由于要模拟运行多个解决方案,而且最后要根据运行结果来进行评价,这就意味着每个方案的后半部分的初始数据应该是一样,也就是说在运行每个方案后半部分之前,要保证数据都是由前半部分运行所产生的数据,当然,咱们这里并不具体的去深入到底有哪些解决方案,也不去深入到底有哪些状态数据,这里只是示意一下。

那么,这样的系统该如何实现呢?尤其是每个方案运行需要的初始数据应该一样,要如何来保证呢?

不用模式的解决方案

 要保证初始数据的一致,实现思路也很简单:

    首先模拟运行流程第一个阶段,得到后阶段各个方案运行需要的数据,并把数据保存下来,以备后用

  • 每次在模拟运行某一个方案之前,用保存的数据去重新设置模拟运行流程的对象,这样运行后面不同的方案时,对于这些方案,初始数据就是一样的了

根据上面的思路,来写出仿真运行的示意代码,示例代码如下:

/**
 * 模拟运行流程A,只是一个示意,代指某个具体流程
 */
public class FlowAMock {
    /**
     * 流程名称,不需要外部存储的状态数据
     */
    private String flowName;
    /**
     * 示意,代指某个中间结果,需要外部存储的状态数据
     */
    private int tempResult;
    /**
     * 示意,代指某个中间结果,需要外部存储的状态数据
     */
    private String tempState;
    /**
     * 构造方法,传入流程名称
     * @param flowName 流程名称
     */
    public FlowAMock(String flowName){
       this.flowName = flowName;
    }
   
    public String getTempState() {
       return tempState;
    }
    public void setTempState(String tempState) {
       this.tempState = tempState;
    }
    public int getTempResult() {
       return tempResult;
    }
    public void setTempResult(int tempResult) {
       this.tempResult = tempResult;
    }
   
    /**
     * 示意,运行流程的第一个阶段
     */
    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;
    }  
}

看看如何使用这个模拟流程的对象,写个客户端来测试一下。示例代码如下:

public class Client {
    public static void main(String[] args) {
       // 创建模拟运行流程的对象
       FlowAMock mock = new FlowAMock("TestFlow");
       //运行流 程的第一个阶段
       mock.runPhaseOne();
       //得到第一个阶段运行所产生的数据,后面要用
       int tempResult = mock.getTempResult();
       String tempState = mock.getTempState();
      
       //按照方案一来运行流程后半部分
       mock.schema1();
      
       //把第一个阶段运行所产生的数据重新设置回去
       mock.setTempResult(tempResult);
       mock.setTempState(tempState);
      
       //按照方案二来运行流程后半部分
       mock.schema2();
    }
}

运行结果如下:

PhaseOne,Schema1 : now run 3
PhaseOne,Schema2 : now run 3

仔细看,上面结果中框住的部分,是一样的值,这说明运行时,它们的初始数据是一样的,基本满足了功能要求。

看起来实现很简单,是吧,想一想有没有什么问题呢?

上面的实现有一个不太好的地方,那就是数据是一个一个零散着在外部存放的,如果需要外部存放的数据多了,会显得很杂乱。这个好解决,只需要定义一个数据对象来封装这些需要外部存放的数据就可以了,上面那样做是故意的,好提醒大家这个问题。这个就不去示例了。

还有一个严重的问题,那就是:为了把运行期间的数据放到外部存储起来,模拟流程的对象被迫把内部数据结构开放出来,这暴露了对象的实现细节,而且也破坏了对象的封装性。本来这些数据只是模拟流程的对象内部数据,应该是不对外的。

那么究竟如何实现这样的功能会比较好呢?

备忘录模式定义

  在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象回复到原先保存的状态。

模式结构和说明

备忘录模式_第1张图片


Memento:

       备忘录。主要用来存储原发器对象的内部状态,但是具体需要存储哪些数据是由原发器对象来决定的。另外备忘录应该只能由原发器对象来访问它内部的数据,原发器外部的对象不应该能访问到备忘录对象的内部数据。

Originator:

       原发器。使用备忘录来保存某个时刻原发器自身的状态,也可以使用备忘录来恢复内部状态。

Caretaker:

       备忘录管理者,或者称为备忘录负责人。主要负责保存备忘录对象,但是不能对备忘录对象的内容进行操作或检查。


备忘录模式示例代码

(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;
    }
}

使用备忘录模式重写示例

       学习了备忘录模式的基本知识过后,来尝试一下,使用备忘录模式把前面的示例重写一下,好看看如何使用备忘录模式。

  • 首先,那个模拟流程运行的对象,就相当于备忘录模式中的原发器;

  • 而它要保存的数据,原来是零散的,现在做一个备忘录对象来存储这些数据,并且把这个备忘录对象实现成为内部类;

  • 当然为了保存这个备忘录对象,还是需要提供管理者对象的;

  • 为了和管理者对象交互,管理者需要知道保存对象的类型,那就提供一个备忘录对象的窄接口来供管理者使用,相当于标识了类型。

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

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

再次实现可撤销操作

       在命令模式中,讲到了可撤销的操作,在那里讲到:有两种基本的思路来实现可撤销的操作,一种是补偿式或者反操作式:比如被撤销的操作是加的功能,那撤消的实现就变成减的功能;同理被撤销的操作是打开的功能,那么撤销的实现就变成关闭的功能。

       另外一种方式是存储恢复式,意思就是把操作前的状态记录下来,然后要撤销操作的时候就直接恢复回去就可以了。

       这里就该来实现第二种方式,就是存储恢复式,为了让大家更好的理解可撤销操作的功能,还是用原来的那个例子,对比学习会比较清楚。

       这也相当于是命令模式和备忘录模式结合的一个例子,而且由于命令列表的存在,对应保存的备忘录对象也是多个。

1:范例需求

考虑一个计算器的功能,最简单的那种,只能实现加减法运算,现在要让这个计算器支持可撤销的操作。

2:存储恢复式的解决方案

       存储恢复式的实现,可以使用备忘录模式,大致实现的思路如下:

  • 把原来的运算类,就是那个Operation类,当作原发器,原来的内部状态result,就只提供一个getter方法,来让外部获取运算的结果

  • 在这个原发器里面,实现一个私有的备忘录对象

  • 把原来的计算器类,就是Calculator类,当作管理者,把命令对应的备忘录对象保存在这里。当需要撤销操作的时候,就把相应的备忘录对象设置回到原发器去,恢复原发器的状态

一起来看看具体的实现,会更清楚。

(1)定义备忘录对象的窄接口,示例代码如下

public interface Memento {    //空的}

(2)定义命令的接口,有几点修改:

  • 修改原来的undo方法,传入备忘录对象

  • 添加一个redo方法,传入备忘录对象

  • 添加一个createMemento的方法,获取需要被保存的备忘录对象

示例代码如下:

/**
 * 定义一个命令的接口
 */
public interface Command {
    /**
     * 执行命令
     */
    public void execute();
    /**
     * 撤销命令,恢复到备忘录对象记录的状态
     * @param m 备忘录对象
     */
    public void undo(Memento m);
    /**
     * 重做命令,恢复到备忘录对象记录的状态
     * @param m 备忘录对象
     */
    public void redo(Memento m);
    /**
     * 创建保存原发器对象的状态的备忘录对象
     * @return 创建好的备忘录对象
     */
    public Memento createMemento();
}

(3)再来定义操作运算的接口,相当于计算器类这个原发器对外提供的接口,它需要做如下的调整:

  • 去掉原有的setResult方法,内部状态,不允许外部操作

  • 添加一个createMemento的方法,获取需要保存的备忘录对象

  • 添加一个setMemento的方法,来重新设置原发器对象的状态

示例代码如下:

/**
 * 操作运算的接口
 */
public interface OperationApi {
    /**
     * 获取计算完成后的结果
     * @return 计算完成后的结果
     */
    public int getResult();
    /**
     * 执行加法
     * @param num 需要加的数
     */
    public void add(int num);
    /**
     * 执行减法
     * @param num 需要减的数
     */
    public void substract(int num);
    /**
     * 创建保存原发器对象的状态的备忘录对象
     * @return 创建好的备忘录对象
     */
    public Memento createMemento();
    /**
     * 重新设置原发器对象的状态,让其回到备忘录对象记录的状态
     * @param memento 记录有原发器状态的备忘录对象
     */
    public void setMemento(Memento memento);
}

(4)由于现在撤销和恢复操作是通过使用备忘录对象,直接来恢复原发器的状态,因此就不再需要按照操作类型来区分了,对于所有的命令实现,它们的撤销和重做都是一样的。原来的实现是要区分的,如果是撤销加的操作,那就是减,而撤销减的操作,那就是加。现在就不区分了,统一使用备忘录对象来恢复。

       因此,实现一个所有命令的公共对象,在里面把公共功能都实现了,这样每个命令在实现的时候就简单了。顺便把设置持有者的公共实现也放到这个公共对象里面来,这样各个命令对象就不用再实现这个方法了,示例代码如下:

/**
 * 命令对象的公共对象,实现各个命令对象的公共方法
 */
public abstract class AbstractCommand implements Command{
    /**
     * 具体的功能实现,这里不管
     */
    public abstract void execute();
    /**
     * 持有真正的命令实现者对象
     */
    protected OperationApi operation = null;
    public void setOperation(OperationApi operation) {
       this.operation = operation;
    }
    public Memento createMemento() {
       return this.operation.createMemento();
    }
    public void redo(Memento m) {
       this.operation.setMemento(m);
    }
    public void undo(Memento m) {
       this.operation.setMemento(m);
    }
}

(5)有了公共的命令实现对象,各个具体命令的实现就简单了,实现加法命令的对象实现,不再直接实现Command接口了,而是继承命令的公共对象,这样只需要实现跟自己命令相关的业务方法就好了,示例代码如下:

public class AddCommand extends AbstractCommand{
    private int opeNum;
    public AddCommand(int opeNum){
       this.opeNum = opeNum;
    }
    public void execute() {
       this.operation.add(opeNum);
    }
}

看看减法命令的实现,跟加法命令的实现差不多,示例代码如下:

public class SubstractCommand extends AbstractCommand{
    
    private int opeNum;
    public SubstractCommand(int opeNum){
        this.opeNum = opeNum;
    }
    public void execute() {
        this.operation.substract(opeNum);
    }
    
}


(6)接下来看看运算类的实现,相当于是原发器对象,它的实现有如下改变:

  • 不再提供setResult方法,内部状态,不允许外部来操作

  • 添加了createMemento和setMemento方法的实现

  • 添加实现了一个私有的备忘录对象

示例代码如下:

/**
 * 运算类,真正实现加减法运算
 */
public class Operation implements OperationApi{
    /**
     * 记录运算的结果
     */
    private int result;
    public int getResult() {
        return result;
    }

    public void add(int num){
        result += num;
    }
    public void substract(int num){
        result -= num;
    }
    /**
     * 结果保存到备忘录实例中
     */
    public Memento createMemento() {
        MementoImpl m = new MementoImpl(result);
        return m;
    }
    /**
     * 使用备忘录中结果重写赋值
     */
    public void setMemento(Memento memento) {
        MementoImpl m = (MementoImpl)memento;
        this.result = m.getResult();
    }
    /**
     * 备忘录对象
     */
    private static class MementoImpl implements Memento{
        private int result = 0;
        public MementoImpl(int result){
            this.result = result;
        }

        public int getResult() {
            return result;
        }
    }
}

(7)接下来该看看如何具体的使用备忘录对象来实现撤销操作和重做操作了。同样在计算器类里面实现,这个时候,计算器类就相当于是备忘录模式管理者对象。

       实现思路:由于对于每个命令对象,撤销和重做的状态是不一样的,撤销是回到命令操作前的状态,而重做是回到命令操作后的状态,因此对每一个命令,使用一个备忘录对象的数组来记录对应的状态。

       这些备忘录对象是跟命令对象相对应的,因此也跟命令历史记录一样,设立相应的历史记录,它的顺序跟命令完全对应起来。在操作命令的历史记录的同时,对应操作相应的备忘录对象记录。

示例代码如下:

/**
 * 计算器类,计算器上有加法按钮、减法按钮,还有撤销和恢复的按钮
 */
public class Calculator {
    /**
     * 命令的操作的历史记录,在撤销时候用
     */
    private List undoCmds = new ArrayList();
    /**
     * 命令被撤销的历史记录,在恢复时候用
     */
    private List redoCmds = new ArrayList();
    /**
     * 命令操作对应的备忘录对象的历史记录,在撤销时候用,
     * 数组有两个元素,第一个是命令执行前的状态,第二个是命令执行后的状态
     */
    private List undoMementos =
new ArrayList();
    /**
     * 被撤销命令对应的备忘录对象的历史记录,在恢复时候用,
     * 数组有两个元素,第一个是命令执行前的状态,第二个是命令执行后的状态
     */
    private List redoMementos =
new ArrayList();
   
    private Command addCmd = null;
    private Command substractCmd = null;
    public void setAddCmd(Command addCmd) {
       this.addCmd = addCmd;
    }
    public void setSubstractCmd(Command substractCmd) {
       this.substractCmd = substractCmd;
    }  
 
    public void addPressed(){
       //获取对应的备忘录对象,并保存在相应的历史记录里面
       Memento m1 = this.addCmd.createMemento();
      
       //执行命令
       this.addCmd.execute();
//把操作记录到历史记录里面
       undoCmds.add(this.addCmd);
 
       //获取执行命令后的备忘录对象
       Memento m2 = this.addCmd.createMemento();
       //设置到撤销的历史记录里面
       this.undoMementos.add(new Memento[]{m1,m2});
    }
    public void substractPressed(){
       //获取对应的备忘录对象,并保存在相应的历史记录里面    
       Memento m1 = this.substractCmd.createMemento();
      
       //执行命令
       this.substractCmd.execute();
//把操作记录到历史记录里面
       undoCmds.add(this.substractCmd);
      
       //获取执行命令后的备忘录对象
       Memento m2 = this.substractCmd.createMemento();
       //设置到撤销的历史记录里面
       this.undoMementos.add(new Memento[]{m1,m2});
    }
    public void undoPressed(){
       if(undoCmds.size()>0){
           //取出最后一个命令来撤销
           Command cmd = undoCmds.get(undoCmds.size()-1);
           //获取对应的备忘录对象
           Memento[] ms = undoMementos.get(undoCmds.size()-1);
          
           //撤销
           cmd.undo(ms[0]);
          
           //如果还有恢复的功能,那就把这个命令记录到恢复的历史记录里面
           redoCmds.add(cmd);
           //把相应的备忘录对象也添加过去
           redoMementos.add(ms);
          
           //然后把最后一个命令删除掉,
           undoCmds.remove(cmd);
           //把相应的备忘录对象也删除掉
           undoMementos.remove(ms);
       }else{
           System.out.println("很抱歉,没有可撤销的命令");
       }
    }
    public void redoPressed(){
       if(redoCmds.size()>0){
           //取出最后一个命令来重做
           Command cmd = redoCmds.get(redoCmds.size()-1);
           //获取对应的备忘录对象
           Memento[] ms = redoMementos.get(redoCmds.size()-1);
          
           //重做
           cmd.redo(ms[1]);
          
           //把这个命令记录到可撤销的历史记录里面
           undoCmds.add(cmd);
           //把相应的备忘录对象也添加过去
           undoMementos.add(ms);
           //然后把最后一个命令删除掉
           redoCmds.remove(cmd);
           //把相应的备忘录对象也删除掉
           redoMementos.remove(ms);
       }else{
           System.out.println("很抱歉,没有可恢复的命令");
       }
    }
}

(8)客户端跟以前的实现没有什么变化,示例代码如下:

public class Client {
    public static void main(String[] args) {
       //1:组装命令和接收者
       //创建接收者
       OperationApi operation = new Operation();
       //创建命令
       AddCommand addCmd = new AddCommand(5);
       SubstractCommand substractCmd = new SubstractCommand(3);
       //组装命令和接收者
       addCmd.setOperation(operation);
       substractCmd.setOperation(operation);
      
       //2:把命令设置到持有者,就是计算器里面
       Calculator calculator = new Calculator();
       calculator.setAddCmd(addCmd);
       calculator.setSubstractCmd(substractCmd);
      
       //3:模拟按下按钮,测试一下
       calculator.addPressed();
       System.out.println("一次加法运算后的结果为:"
+operation.getResult());
       calculator.substractPressed();
       System.out.println("一次减法运算后的结果为:"
+operation.getResult());
      
       //测试撤消
       calculator.undoPressed();
       System.out.println("撤销一次后的结果为:"
+operation.getResult());
       calculator.undoPressed();
       System.out.println("再撤销一次后的结果为:"
+operation.getResult());
      
       //测试恢复
       calculator.redoPressed();
       System.out.println("恢复操作一次后的结果为:"
+operation.getResult());
       calculator.redoPressed();
       System.out.println("再恢复操作一次后的结果为:"
+operation.getResult());
    }
}

运行结果,示例如下:示例代码如下:

一次加法运算后的结果为:5
一次减法运算后的结果为:2
撤销一次后的结果为:5
再撤销一次后的结果为:0
恢复操作一次后的结果为:5
再恢复操作一次后的结果为:2

跟前面采用补偿式或者反操作式得到的结果是一样的。好好体会一下,对比两种实现方式,看看都是怎么实现的。顺便也体会一下命令模式和备忘录模式是如何结合起来实现功能的。

备忘录模式的优缺点

   更好的封装性;
      备忘录模式通过使用备忘录对象,来封装原发器对象的内部状态,虽然这个对象是保存在原发器对象的外部,但是由于备忘录对象的窄接口并不提供任何方法,这样有效的保证了对原发器对象内部状态的封装,不把原发器对象的内部实现细节暴露给外部。

   简化了原发器
     备忘录模式中,备忘录对象被保存到原发器对象之外,让客户来管理他们请求的状态,从而让原发器对象得到简化。

  窄接口和宽接口
     备忘录模式,通过引入窄接口和宽接口,使得不同的地方,对备忘录对象的访问是不一样的。窄接口保证了只有原发器才可以访问备忘录对象的状态。

   可能会导致高开销
     备忘录模式基本的功能,就是对备忘录对象的存储和恢复,它的基本实现方式就是缓存备忘录对象。这样一来,如果需要缓存的数据量很大,或者是特别频繁的创建备忘录对象,开销是很大的。


相关模式

 备忘录模式和命令模式
    这两个模式可以组合使用。
    命令模式实现中,在实现命令的撤销和重做的时候,可以使用备忘录模式,在命令操作的时候记录下操作前后的状态,然后在命令撤销和重做的时候,直接使用相应的备忘录对象来恢复状态就可以了。
    在这种撤销的执行顺序和重做执行顺序可控的情况下,备忘录对象还可以采用增量式记录的方式,可以减少缓存的数据量。

  备忘录模式和原型模式
    这两个模式可以组合使用。
    在原发器对象创建备忘录对象的时候,如果原发器对象中全部或者大部分的状态都需要保存,一个简洁的方式就是直接克隆一个原发器对象。也就是说,这个时候备忘录对象里面存放的是一个原发器对象的实例



转载至:http://sishuok.com/forum/blogPost/list/5633.html

   cc老师的设计模式是我目前看过最详细最有实践的教程。