👉文章示例代码👈

定义

在不破坏封装性的前提下,捕获一个对象的内部状态,并且在该对象之外保存这个状态,从而可以在将来合适的时候把这个对象还原到之前保存的状态。

备忘录模式也叫快照模式

三个角色

备忘录模式主要的角色有以下三个:

  • 发起人Originator:负责创建一个备忘录,记录当前时刻发起人的内部状态,同时提供恢复备忘录数据的能力。
  • 备忘录Memento:负责存储发起人对象的内部状态。
  • 管理者Caretaker:对备忘录进行管理,提供保存与获取备忘录的功能,但是不能对备忘录的内容进行操作或者检查。

场景示例

我们在使用 ​IntelliJ IDEA 进行开发的时候,IntelliJ IDEA 会将所有的更改进行自动保存。而且在我们开发的任何阶段我们都可以对不必要的更改进行还原操作,让任何文件或者目录都可以恢复到更改之前的任何状态。

笔者这里就 IntelliJ IDEA 的保存和还原更改为例来进行简单的示例说明。

创建发起人

在该示例当中,我们是对代码文件进行备份,所以其可以作为是一个发起人角色。

/**
 * @author zhh
 * @description 代码文件(发起人)
 * @date 2020-03-02 11:22
 */
public class CodeFile {

    /**
     * 类名
     */
    private String className;

    /**
     * 内容
     */
    private String content;

    /**
     * 备份编号
     */
    private String code;

    public CodeFile(String className, String content, String code) {
        this.className = className;
        this.content = content;
        this.code = code;
    }

    // 此处省略getter、setter方法

    /**
     * 创建代码文件备份
     */
    public CodeFileMemento saveToMemento() {
        System.out.println("创建了一个新的备份, 备份编号: " + this.code);
        return new CodeFileMemento(this.className, this.content, this.code);
    }

    /**
     * 撤销至某个备份
     * @param codeFileMemento 代码文件备份
     */
    public void undoFromMemento(CodeFileMemento codeFileMemento) {
        System.out.println("撤销代码文件至原先备份, 内容为: " + codeFileMemento);
        this.className = codeFileMemento.getClassName();
        this.content = codeFileMemento.getContent();
        this.code = codeFileMemento.getCode();
    }

    @Override
    public String toString() {
        return "CodeFile{" + "className='" + className + '\'' + ", content='" + content + '\'' + ", code='" + code +
                '\'' + '}';
    }
}

创建备忘录

/**
 * @author zhh
 * @description 代码文件备份
 * @date 2020-03-02 11:26
 */
public class CodeFileMemento {

    /**
     * 类名
     */
    private String className;

    /**
     * 内容
     */
    private String content;

    /**
     * 备份编号
     */
    private String code;

    public CodeFileMemento(String className, String content, String code) {
        this.className = className;
        this.content = content;
        this.code = code;
    }

    public String getClassName() {
        return className;
    }

    public String getContent() {
        return content;
    }

    public String getCode() {
        return code;
    }

    @Override
    public String toString() {
        return "CodeFileMemento{" + "className='" + className + '\'' + ", content='" + content + '\'' + ", code='" +
                code + '\'' + '}';
    }
}

创建管理者

/**
 * @author zhh
 * @description 代码文件备份管理器
 * @date 2020-03-02 11:31
 */
public class CodeFileMementoManager {

    private final Stack<CodeFileMemento> CODE_FILE_MEMENTO_STACK = new Stack<CodeFileMemento>();

    /**
     * 获取代码文件备份
     */
    public CodeFileMemento getCodeFileMemento() {
        return CODE_FILE_MEMENTO_STACK.pop();
    }

    /**
     * 添加代码文件备份
     * @param codeFileMemento 代码文件备份
     */
    public void addCodeFileMemento(CodeFileMemento codeFileMemento) {
        CODE_FILE_MEMENTO_STACK.push(codeFileMemento);
    }
}

测试类及输出

/**
 * @author zhh
 * @description 测试类
 * @date 2020-03-02 11:36
 */
public class Test {

    public static void main(String[] args) {
        CodeFileMementoManager codeFileMementoManager = new CodeFileMementoManager();

        // 代码文件进行备份
        CodeFile codeFile = new CodeFile("Test", "public class Test {}", "bak-0001");
        CodeFileMemento codeFileMemento = codeFile.saveToMemento();
        codeFileMementoManager.addCodeFileMemento(codeFileMemento);

        // 修改代码文件内容
        codeFile.setContent("public class Test {public static void main(String[] args) {}}");
        codeFile.setCode("bak-0002");

        System.out.println("当前文件内容: " + codeFile);

        // 后悔了, 进行撤销操作
        CodeFileMemento rollbackMemento = codeFileMementoManager.getCodeFileMemento();
        codeFile.undoFromMemento(rollbackMemento);
        System.out.println("执行撤销操作后, 当前文件内容: " + codeFile);
    }
}

测试类的输出结果如下:

创建了一个新的备份, 备份编号: bak-0001

当前文件内容: CodeFile{className=’Test’, content=’public class Test {public static void main(String[] args) {}}’, code=’bak-0002′}

撤销代码文件至原先备份, 内容为: CodeFileMemento{className=’Test’, content=’public class Test {}’, code=’bak-0001′}

执行撤销操作后, 当前文件内容: CodeFile{className=’Test’, content=’public class Test {}’, code=’bak-0001′}

类结构图

以上示例类的结构图如下所示

image.png

从类图中我们可以看到,我们的 ​CodeFileMemento (备忘录角色)只能由我们的 ​CodeFile (发起人角色)来创建,而 CodeFileMemento (备忘录角色)和 CodeFileMementoManager (管理者角色)之间是一个聚合关系。

总结

适用场景

  • 需要保存与恢复数据的相关业务场景。
  • 需要提供一个可以回滚操作的场景,即可以恢复到之前保存的状态。

优点

  • 为用户提供了一种可以恢复状态的机制。
  • 保持关键对象的数据封装。

缺点

对象需要保存的内部状态信息过多或者操作频繁时,会占用比较大的内存资源。

参考