一天一模式之18备忘录模式

原博文:https://blog.csdn.net/KongZhongNiao/article/details/80497646

学习备忘录模式

一:初识备忘录模式

包括:定义、结构、参考实现

二:体会备忘录模式

包括:场景问题、不用模式的解决方案、使用模式的解决方案

三:理解备忘录模式

包括:认识备忘录模式、结合原型模式 、离线存储 、

再次实现可撤销操作、备忘录模式的优缺点
四:思考备忘录模式

包括:备忘录模式的本质、何时选用

初识备忘录模式

定义

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

结构和说明

示例
备忘录的窄接口,没有任何方法定义
package cn.javass.dp.memento.example2;
/**
 * 备忘录的窄接口,没有任何方法定义
 */
public interface Memento {

}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
原发器对象
package cn.javass.dp.memento.example2;

/**
 * 原发器对象
 */
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;
        }
    }

}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
负责保存备忘录的对象
package cn.javass.dp.memento.example2;
import java.util.*;
/**
 * 负责保存备忘录的对象
 */
public class Caretaker{
    /**
     * 记录被保存的备忘录对象
     */
    private Memento memento = null;
    /**
     * 保存备忘录对象
     * @param memento 被保存的备忘录对象
     */
    public void saveMemento(Memento memento){
        this.memento = memento;
    }
    /**
     * 获取被保存的备忘录对象
     * @return 被保存的备忘录对象
     */
    public Memento retriveMemento(){
        return this.memento;
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

体会备忘录模式

Memento:

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

Originator:

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

Caretaker:

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

开发仿真系统

考虑这样一个仿真应用,功能是:模拟运行针对某个具体问题的多个解决

方案,记录运行过程的各种数据,在模拟运行完成过后,好对这多个解决方案进
行比较和评价,从而选定最优的解决方案。
由于都是解决同一个具体的问题,这多个解决方案并不是完全不一样的,

假定它们的前半部分运行是完全一样的,只是在后半部分采用了不同的解决方
案,后半部分需要使用前半部分运行所产生的数据。
由于要模拟运行多个解决方案,而且最后要根据运行结果来进行评价,这

就意味着每个方案的后半部分的初始数据应该是一样。
那么,这样的系统该如何实现呢?尤其是每个方案运行需要的初始数据应

该一样,要如何来保证呢?

不用模式的解决方案

要保证初始数据的一致,实现思路也很简单:
- 1:首先模拟运行流程第一个阶段,得到后阶段各个方案运行需要的数据,并把数据
保存下来,以备后用
- 2:每次在模拟运行某一个方案之前,用保存的数据去重新设置模拟运行流程的对
象,这样运行后面不同的方案时,对于这些方案,初始数据就是一样的了

示例
模拟运行流程A,只是一个示意,代指某个具体流程
package cn.javass.dp.memento.example1;
/**
 * 模拟运行流程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;
    }   
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
客户端
package cn.javass.dp.memento.example1;

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

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

有何问题

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

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

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

使用模式来解决的思路

在这个示例中出现的、需要解决的问题就是:如何能够在不破坏对象的封

装性的前提下,来保存和恢复对象的状态。

备忘录模式引入一个存储状态的备忘录对象,为了让外部无法访问这个对
象的值,一般把这个对象实现成为需要保存数据的对象的内部类,通常还是私有
的,这样一来,除了这个需要保存数据的对象,外部无法访问到这个备忘录对象
的数据,这就保证了对象的封装性不被破坏。

但是这个备忘录对象需要存储在外部,为了避免让外部访问到这个对象内
部的数据,备忘录模式引入了一个备忘录对象的窄接口,这个接口一般是空的,
什么方法都没有,这样外部存储的地方,只是知道存储了一些备忘录接口的对
象,但是由于接口是空的,它们无法通过接口去访问备忘录对象内的数据。

使用模式的解决方案的类图

示例
模拟运行流程A的对象的备忘录接口,是个窄接口
package cn.javass.dp.memento.example3;
import java.io.*;
/**
 * 模拟运行流程A的对象的备忘录接口,是个窄接口
 */
public interface FlowAMockMemento extends java.io.Serializable{
    //空的
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
模拟运行流程A,只是一个示意,代指某个具体流程(内部数据备忘)
package cn.javass.dp.memento.example3;

import java.io.Serializable;

/**
 * 模拟运行流程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;
        }
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
模拟运行流程A,只是一个示意,代指某个具体流程(类拷贝)
package cn.javass.dp.memento.example3;

import java.io.Serializable;

/**
 * 模拟运行流程A,只是一个示意,代指某个具体流程
 */
public class FlowAMockPrototype implements Cloneable {
    /**
     * 流程名称,不需要外部存储的状态数据
     */
    private String flowName;
    /**
     * 示意,代指某个中间结果,需要外部存储的状态数据
     */
    private int tempResult;
    /**
     * 示意,代指某个中间结果,需要外部存储的状态数据
     */
    private String tempState;
    /**
     * 构造方法,传入流程名称
     * @param flowName 流程名称
     */
    public FlowAMockPrototype(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() {
        try {
            return new MementoImplPrototype((FlowAMockPrototype) this.clone());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 重新设置原发器对象的状态,让其回到备忘录对象记录的状态
     * @param memento 记录有原发器状态的备忘录对象
     */
    public void setMemento(FlowAMockMemento memento) {
        MementoImplPrototype mementoImpl = (MementoImplPrototype)memento;
        this.tempResult = mementoImpl.getFlowAMock().tempResult;
        this.tempState = mementoImpl.getFlowAMock().tempState;
    }
    /**
     * 真正的备忘录对象,实现备忘录窄接口
     * 实现成私有的内部类,不让外部访问
     */
    private static class MementoImplPrototype implements FlowAMockMemento{
        private FlowAMockPrototype flowAMock = null;

        public MementoImplPrototype(FlowAMockPrototype f){
            this.flowAMock = f;
        }

        public FlowAMockPrototype getFlowAMock() {
            return flowAMock;
        }
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
客户端
package cn.javass.dp.memento.example3;

public class Client {
    public static void main(String[] args) {
        // 创建模拟运行流程的对象
        FlowAMock mock = new FlowAMock("TestFlow");
        //运行流程的第一个阶段
        mock.runPhaseOne();

        //创建一个管理者
        FlowAMementoFileCareTaker careTaker = new FlowAMementoFileCareTaker();
        //创建此时对象的备忘录对象,并保存到管理者对象那里,后面要用
        FlowAMockMemento memento = mock.createMemento();
        careTaker.saveMemento(memento);

        //按照方案一来运行流程后半部分
        mock.schema1();

        //从管理者获取备忘录对象,然后设置回去,
        //让模拟运行流程的对象自己恢复自己的内部状态
        mock.setMemento(careTaker.retriveMemento());

        //按照方案二来运行流程后半部分
        mock.schema2();
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

理解备忘录模式

认识备忘录模式

1:备忘录模式的功能

备忘录模式的功能,首先是在不破坏封装性的前提下,捕获一个对象的内部状
态。这里要注意两点,一个是不破坏封装性,也就是对象不能暴露它不应该暴露的细
节;另外一个是捕获的是对象的内部状态,而且通常还是运行期间某个时刻,对象的
内部状态。

为什么要捕获这个对象的内部状态呢?捕获这个内部状态有什么用呢?

是要在以后的某个时候,将该对象的状态恢复到备忘录所保存的状态,这才是
备忘录真正的目的,前面保存状态就是为了后面恢复,虽然不是一定要恢复,但是目
的是为了恢复。这也是很多人理解备忘录模式的时候,忽视掉的地方,他们太关注备
忘,而忽视了恢复,这是不全面的理解。

捕获的状态存放在哪里呢?

备忘录模式中,捕获的内部状态,存储在备忘录对象中;而备忘录对象,通常
会被存储在原发器对象之外,也就是被保存状态的对象的外部,通常是存放在管理者
对象哪里。

2:备忘录对象

在备忘录模式中,备忘录对象,通常就是用来记录原发器需要保存的状态
的对象,简单点的实现,也就是个封装数据的对象。

但是这个备忘录对象和普通的封装数据的对象还是有区别的,主要就是这
个备忘录对象,一般只让原发器对象来操作,而不是像普通的封装数据的对象那
样,谁都可以使用。为了保证这一点,通常会把备忘录对象作为原发器对象的内
部类来实现,而且会实现成私有的,这就断绝了外部来访问这个备忘录对象的途
径。

但是备忘录对象需要保存在原发器对象之外,为了与外部交互,通常备忘
录对象都会实现一个窄接口,来标识对象的类型。。

3:原发器对象

原发器对象,就是需要被保存状态的对象,也是有可能需要恢复状态的对
象。原发器一般会包含备忘录对象的实现。

通常原发器对象应该提供捕获某个时刻对象内部状态的方法,在这个方法
里面,原发器对象会创建备忘录对象,把需要保存的状态数据设置到备忘录对象
中,然后把备忘录对象提供给管理者对象来保存。

当然,原发器对象也应该提供这样的方法:按照外部要求来恢复内部状态
到某个备忘录对象记录的状态。

4:管理者对象

在备忘录模式中,管理者对象,主要是负责保存备忘录对象,这里有几点

要讲一下。
- (1):并不一定要特别的做出一个管理者对象来,广义地说,调用原发器获得备忘
录对象后,备忘录对象放在哪里,哪个对象就可以算是管理者对象。
- (2):管理者对象并不是只能管理一个备忘录对象,一个管理者对象可以管理很多
的备忘录对象,虽然前面的示例中是保存一个备忘录对象,别忘了那只是个示
意,并不是只能实现成那样。
- (3):狭义的管理者对象,是只管理同一类的备忘录对象,但是广义管理者对象是
可以管理不同类型的备忘录对象的。
- (4):管理者对象需要实现的基本功能主要就是:存入备忘录对象、保存备忘录对
象、获取备忘录对象,如果从功能上看,就是一个缓存功能的实现,或者是一个
简单的对象实例池的实现。
- (5):管理者虽然能存取备忘录对象,但是不能访问备忘录对象内部的数据。

5:窄接口和宽接口

窄接口:管理者只能看到备忘录的窄接口,它的实现里面通常没有任何的

方法,只是一个类型标识,窄接口使得管理者只能将备忘录传递给其它对象。
宽接口:原发器能够看到一个宽接口,允许它访问所需的所有数据,来返
回到先前的状态。理想状况是:只允许生成备忘录的原发器来访问该备忘录的内
部状态,通常实现成为原发器内的一个私有内部类。

备忘录模式的标准实现方式,那就是窄接口没有任何的方法,把备忘录对
象实现成为原发器对象的私有内部类。那么能不能在窄接口里面提供备忘录对象
对外的方法,变相对外提供一个“宽”点的接口呢?

通常情况是不会这么做的,因为这样一来,所有能拿到这个接口的对象就
可以通过这个接口来访问备忘录内部的数据或是功能,这违反了备忘录模式的初
衷,备忘录模式要求“在不破坏封装性的前提下”,如果这么做,那就等于是暴
露了内部细节,因此,备忘录模式在实现的时候,对外多是采用窄接口,而且通
常不会定义任何方法。

6:使用备忘录的潜在代价

标准的备忘录模式的实现机制是依靠缓存来实现的,因此,当需要备忘的数据
量较大时,或者是存储的备忘录对象数据量不大但是数量很多的时候,或者是用户很
频繁的创建备忘录对象的时候,这些都会导致非常大的开销。

因此在使用备忘录模式的时候,一定要好好思考应用的环境,如果使用的代价
太高,就不要选用备忘录模式,可以采用其它的替代方案

7:增量存储

如果需要频繁的创建备忘录对象,而且创建和应用备忘录对象来恢复状态的顺
序是可控的,那么可以让备忘录进行增量存储,也就是备忘录可以仅仅存储原发器内
部相对于上一次存储状态后的增量改变。

比如:在命令模式实现可撤销命令的实现中,就可以使用备忘录来保存每个命
令对应的状态,然后在撤销命令的时候,使用备忘录来恢复这些状态。由于命令的历
史列表是按照命令操作的顺序来存放的,也是按照这个历史列表来进行取消和重做
的,因此顺序是可控的。那么这种情况,还可以让备忘录对象只存储一个命令所产生
的增量改变而不是它所影响的每一个对象的完整状态。

8:备忘录模式调用顺序示意图

在使用备忘录模式的时候,分成了两个阶段,第一个阶段是创建备忘录对

象的阶段,第二个阶段是使用备忘录对象来恢复原发器对象的状态的阶段。它们
的调用顺序是不一样的,下面分开用图来示意一下

使用备忘录对象来恢复原发器对象的状态的阶段,调用顺序如图

结合原型模式

在原发器对象创建备忘录对象的时候,如果原发器对象中全部或者大部分

的状态都需要保存,一个简洁的方式就是直接克隆一个原发器对象。也就是说,
这个时候备忘录对象里面存放的是一个原发器对象的实例 。

示例
package cn.javass.dp.memento.example3;

import java.io.Serializable;

/**
 * 模拟运行流程A,只是一个示意,代指某个具体流程
 */
public class FlowAMockPrototype implements Cloneable {
    /**
     * 流程名称,不需要外部存储的状态数据
     */
    private String flowName;
    /**
     * 示意,代指某个中间结果,需要外部存储的状态数据
     */
    private int tempResult;
    /**
     * 示意,代指某个中间结果,需要外部存储的状态数据
     */
    private String tempState;
    /**
     * 构造方法,传入流程名称
     * @param flowName 流程名称
     */
    public FlowAMockPrototype(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() {
        try {
            return new MementoImplPrototype((FlowAMockPrototype) this.clone());
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 重新设置原发器对象的状态,让其回到备忘录对象记录的状态
     * @param memento 记录有原发器状态的备忘录对象
     */
    public void setMemento(FlowAMockMemento memento) {
        MementoImplPrototype mementoImpl = (MementoImplPrototype)memento;
        this.tempResult = mementoImpl.getFlowAMock().tempResult;
        this.tempState = mementoImpl.getFlowAMock().tempState;
    }
    /**
     * 真正的备忘录对象,实现备忘录窄接口
     * 实现成私有的内部类,不让外部访问
     */
    private static class MementoImplPrototype implements FlowAMockMemento{
        private FlowAMockPrototype flowAMock = null;

        public MementoImplPrototype(FlowAMockPrototype f){
            this.flowAMock = f;
        }

        public FlowAMockPrototype getFlowAMock() {
            return flowAMock;
        }
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91

离线存储

标准的备忘录模式,没有讨论离线存储的实现。

事实上,从备忘录模式的功能和实现上,是可以把备忘录的数据实现成为
离线存储的,也就是不仅限于存储于内存中,可以把这些备忘数据存储到文件
中、xml中、数据库中,从而支持跨越会话的备份和恢复功能。

离线存储甚至能帮助应对应用崩溃,然后关闭重启的情况,应用重启过
后,从离线存储里面获取相应的数据,然后重新设置状态,恢复到崩溃前的状
态。

示例
package cn.javass.dp.memento.example3;

import java.io.*;
/**
 * 负责在文件中保存模拟运行流程A的对象的备忘录对象
 */
public class FlowAMementoFileCareTaker {

    /**
     * 保存备忘录对象
     * @param memento 被保存的备忘录对象
     */
    public void saveMemento(FlowAMockMemento memento){
        //写到文件中
        ObjectOutputStream out = null;
        try{
            out = new ObjectOutputStream(
                    new BufferedOutputStream(
                            new FileOutputStream("FlowAMemento")
                    )
            );
            out.writeObject(memento);
            System.out.println("now write file==========");
        }catch(Exception err){
            err.printStackTrace();
        }finally{
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 获取被保存的备忘录对象
     * @return 被保存的备忘录对象
     */
    public FlowAMockMemento retriveMemento(){
        FlowAMockMemento memento = null;
        //从文件中获取备忘录数据
        ObjectInputStream in = null;
        try{
            in = new ObjectInputStream(
                    new BufferedInputStream(
                            new FileInputStream("FlowAMemento")
                    )
            );
            memento = (FlowAMockMemento)in.readObject();
        }catch(Exception err){
            err.printStackTrace();
        }finally{
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return memento;
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61

再次实现可撤销操作

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

1:范例需求

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

2:存储恢复式的解决方案
  • (1)把原来的运算类,就是那个Operation类,当作原发器,原来的内部状态
    result,就只提供一个getter方法,来让外部获取运算的结果
  • (2)在这个原发器里面,实现一个私有的备忘录对象
  • (3)把原来的计算器类,就是Calculator类,当作管理者,把命令对应的备忘录对
    象保存在这里。当需要撤销操作的时候,就把相应的备忘录对象设置回到原发器
    去,恢复原发器的状态
示例
Memento
package cn.javass.dp.memento.example4;

public interface Memento {
    //空的
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
操作运算的接口
package cn.javass.dp.memento.example4;

/**
 * 操作运算的接口
 */
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);
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
运算类,真正实现加减法运算
package cn.javass.dp.memento.example4;
/**
 * 运算类,真正实现加减法运算
 */
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;
        }
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
定义一个命令的接口
package cn.javass.dp.memento.example4;
/**
 * 定义一个命令的接口
 */
public interface Command {
    /**
     * 执行命令
     */
    public void execute();
    /**
     * 撤销命令,恢复到备忘录对象记录的状态
     * @param m 备忘录对象
     */
    public void undo(Memento m);
    /**
     * 重做命令,恢复到备忘录对象记录的状态
     * @param m 备忘录对象
     */
    public void redo(Memento m);
    /**
     * 创建保存原发器对象的状态的备忘录对象
     * @return 创建好的备忘录对象
     */
    public Memento createMemento();
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
命令对象的公共对象,实现各个命令对象的公共方法
package cn.javass.dp.memento.example4;
/**
 * 命令对象的公共对象,实现各个命令对象的公共方法
 */
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);
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
加法命令
package cn.javass.dp.memento.example4;

public class AddCommand extends AbstractCommand{

    private int opeNum;
    public AddCommand(int opeNum){
        this.opeNum = opeNum;
    }   

    public void execute() {
        this.operation.add(opeNum);
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
减法命令
package cn.javass.dp.memento.example4;

public class SubstractCommand extends AbstractCommand{

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

}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
计算器类,计算器上有加法按钮、减法按钮,还有撤销和恢复的按钮
package cn.javass.dp.memento.example4;
import java.util.*;
/**
 * 计算器类,计算器上有加法按钮、减法按钮,还有撤销和恢复的按钮
 */
public class Calculator {
    /**
     * 命令的操作的历史记录,在撤销时候用
     */
    private List<Command> undoCmds = new ArrayList<Command>();
    /**
     * 命令被撤销的历史记录,在恢复时候用
     */
    private List<Command> redoCmds = new ArrayList<Command>();
    /**
     * 命令操作对应的备忘录对象的历史记录,在撤销时候用
     * 由于对于每个命令对象,撤销和重做的状态是不一样的,
     * 撤销是回到命令操作前的状态,而重做是回到命令操作后的状态,
     * 因此对每一个命令,使用一个备忘录对象的数组来记录对应的状态
     */
    private List<Memento[]> undoMementos = new ArrayList<Memento[]>();
    /**
     * 被撤销命令对应的备忘录对象的历史记录,在恢复时候用,
     * 数组有两个元素,第一个是命令执行前的状态,第二个是命令执行后的状态
     */
    private List<Memento[]> redoMementos = new ArrayList<Memento[]>();

    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("很抱歉,没有可恢复的命令");
        }
    }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
客户端
package cn.javass.dp.memento.example4;

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

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

备忘录模式的优缺点

  • 1:更好的封装性
  • 2:简化了原发器
  • 3:窄接口和宽接口

- 4:可能会导致高开销

思考备忘录模式

备忘录模式的本质

备忘录模式的本质是:保存和恢复内部状态

何时选用备忘录模式

1:如果必须保存一个对象在某一个时刻的全部或者部分状态,这样在以后需要的时
候,可以把该对象恢复到先前的状态。可以使用备忘录模式,使用备忘录对象来
封装和保存需要保存的内部状态,然后把备忘录对象保存到管理者对象里面,在
需要的时候,再从管理者对象里面获取备忘录对象,来恢复对象的状态

2:如果需要保存一个对象的内部状态,但是如果用接口来让其它对象直接得到这些
需要保存的状态,将会暴露对象的实现细节并破坏对象的封装性。可以使用备忘
录模式,把备忘录对象实现成为原发器对象的内部类,而且还是私有的,从而保
证只有原发器对象才能访问该备忘录对象。这样既保存了需要保存的状态,又不
会暴露原发器对象的内部实现细节。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Famiglistimott

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值