设计模式-备忘录模式

备忘录模式

1. 什么是备忘录模式?

想象一下你在玩一个有存档功能的游戏。当你觉得当前进度不错,或者要进行一个有风险的操作前,你会选择"存档"。这个"存档"就保存了你当前游戏的所有状态(比如角色位置、等级、物品栏等)。如果后续操作失败或者你想回到之前的状态,你就可以"读档",恢复到存档时的状态。

备忘录模式 就是这样一种行为型设计模式,它的核心思想是:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

简单来说,它提供了一种状态保存和恢复的机制,允许你"撤销"操作或回到历史某个时间点的状态。

2. 备忘录模式的结构 (主要角色):

  • Originator (发起人/源对象):

    • 这是我们想要保存其状态的对象。

    • 它知道如何创建备忘录 (Memento) 来保存其当前状态。

    • 它也知道如何从备忘录中恢复其之前的状态。

    • 重要: Originator 内部的状态通常是私有的,不直接暴露给外部。

  • Memento (备忘录):

    • 这是一个简单的对象,用于存储 Originator 的内部状态。

    • 核心特点:

      • 它对 Originator 是"宽接口":Originator 可以访问 Memento 内部的所有数据,以便保存和恢复状态。

      • 它对其他对象(特别是 Caretaker)是"窄接口":Caretaker 只能持有 Memento 对象,但不能访问或修改 Memento 的内部状态。这是为了保护封装性。

    • Memento 自身不应该包含任何业务逻辑,它只是一个状态的快照容器。

  • Caretaker (负责人/管理者):

    • 负责保存 Memento 对象。

    • 不知道 Memento 内部的具体内容,也不关心。它只是像保管箱一样持有 Memento。

    • 当需要恢复状态时,它会将之前保存的 Memento 交还给 Originator。

    • Caretaker 可以保存多个 Memento,从而实现多步撤销或历史记录功能。

结构图示:

复制代码
+-----------------+      creates      +-----------------+
|   Originator    |<------------------|     Memento     |
| - state         |                   | - state         |
| + setState()    |                   | + getState()    |  <-- (Usually package-private or accessible only to Originator)
| + createMemento()|                   |                 |
| + restore(m)    |                   +-----------------+
+-----------------+                         ^
        |                                   | (holds)
        | uses                              |
        V                                   |
+-----------------+                         |
|    Caretaker    |-------------------------+
| - mementoList   |
| + saveMemento(m)|
| + getMemento(idx)|
+-----------------+

实现窄接口和宽接口的技巧:

在 Java 中,为了实现 Memento 对 Originator 的宽接口和对 Caretaker 的窄接口,有几种常见做法:

  1. 内部类 (Inner Class):

    • 将 Memento 定义为 Originator 的一个内部类(甚至是私有静态内部类)。

    • 这样,Originator 可以访问 Memento 的所有成员(即使是私有的),而 Caretaker 只能通过 Memento 暴露的公共接口(如果有的话,但通常 Memento 对 Caretaker 没有任何有意义的公共方法)或者仅仅是持有 Memento 的引用。

  2. 包级私有 (Package-Private) 接口:

    • 定义一个接口 MementoInterface,只包含 Caretaker 需要的方法(通常是空的,或者只有标记作用)。

    • 让 Memento 类实现这个接口,并且 Memento 类的 getState() 等方法设置为包级私有或 protected。

    • Originator 和 Memento 放在同一个包下,这样 Originator 可以访问 Memento 的包级私有成员。Caretaker 只能通过 MementoInterface 来引用 Memento。

  3. 标记接口 (Marker Interface) + 封装:

    • Memento 实现一个空的标记接口。

    • Memento 的状态获取方法对 Originator 可见(例如,通过构造函数传入,或者 Originator 具有特殊权限访问)。

内部类是最常见且简洁的实现方式。

3. 备忘录模式的优缺点:

优点:

  • 封装性: 保持了 Originator 内部状态的封装。Caretaker 和其他客户端代码不需要知道 Originator 的内部结构。

  • 简化 Originator: Originator 不需要自己管理其历史状态,将状态的保存和恢复逻辑与自身核心业务逻辑解耦。

  • 状态恢复: 提供了方便的状态恢复机制,可以实现撤销/重做、回滚等功能。

  • 高内聚,低耦合: Originator 和 Memento 紧密相关,但它们与 Caretaker 之间的耦合度较低。

缺点:

  • 资源消耗: 如果 Originator 的状态非常复杂,或者需要保存大量的历史状态,那么创建和存储 Memento 对象可能会消耗大量的内存。需要谨慎管理 Memento 的生命周期。

  • 实现细节: 如果 Originator 的内部状态非常多,创建 Memento 的时候需要复制所有相关状态,可能会比较繁琐。

  • 可能破坏封装(如果 Memento 接口设计不当): 如果 Memento 暴露了过多的内部状态给非 Originator 对象,可能会破坏 Originator 的封装性。所以 Memento 的接口设计很重要。

4. 备忘录模式的应用场景:

  • 需要保存和恢复对象历史状态的场景。

  • 实现撤销/重做 (Undo/Redo) 功能: 文本编辑器、绘图软件、IDE 中的操作撤销。

  • 数据库事务的回滚 (Rollback) 机制的简化模型。

  • 游戏存档和读档功能。

  • 配置信息的快照和恢复: 当用户修改配置后,可以恢复到之前的某个配置版本。

  • 状态机中,需要回溯到某个先前状态时。

代码示例 (使用内部类实现 Memento):

假设我们有一个文本编辑器 Editor,它可以输入文本,并支持撤销操作。

复制代码
import java.util.Stack;
​
// Memento (备忘录) - 作为 Editor 的静态内部类
class Editor {
    private String content; // Originator 的状态
​
    public Editor() {
        this.content = "";
    }
​
    public void type(String words) {
        this.content += words;
    }
​
    public String getContent() {
        return content;
    }
​
    // 创建备忘录,保存当前状态
    public EditorMemento save() {
        return new EditorMemento(this.content);
    }
​
    // 从备忘录恢复状态
    public void restore(EditorMemento memento) {
        if (memento != null) {
            this.content = memento.getSavedContent();
        }
    }
​
    // Memento 内部类
    // 只有 Editor 类可以访问其 getSavedContent() 方法(如果设置为 private,则通过 Editor 访问)
    // 或者可以设为包级私有,或者像这里一样,通过外部类间接控制访问
    public static class EditorMemento { // 为了简单,这里设为 public static 内部类
        private final String savedContent; // Memento 存储的状态
​
        private EditorMemento(String contentToSave) {
            this.savedContent = contentToSave;
        }
​
        // 这个方法理论上应该只被 Originator (Editor) 调用
        // 如果 EditorMemento 不是 public static,而是非静态内部类,Editor可以直接访问 savedContent
        // 如果是 private static 内部类,Editor 也可以访问
        // 这里为了Caretaker能从Editor获取,Editor能从Caretaker设置,做了一些简化
        private String getSavedContent() {
            return savedContent;
        }
    }
}
​
// Caretaker (负责人)
class History {
    private Stack<Editor.EditorMemento> history = new Stack<>(); // 用栈来保存历史记录
​
    public void save(Editor editor) {
        history.push(editor.save()); // 从 Originator 获取 Memento 并保存
    }
​
    public void undo(Editor editor) {
        if (!history.isEmpty()) {
            Editor.EditorMemento lastMemento = history.pop(); // 取出最近的 Memento
            editor.restore(lastMemento); // Originator 从 Memento 恢复状态
        } else {
            System.out.println("Nothing to undo.");
        }
    }
}
​
​
public class MementoPatternDemo {
    public static void main(String[] args) {
        Editor editor = new Editor();
        History history = new History();
​
        // 第一次操作
        editor.type("This is the first sentence. ");
        history.save(editor); // 保存状态1
        System.out.println("Current Content: " + editor.getContent());
​
        // 第二次操作
        editor.type("This is the second sentence. ");
        history.save(editor); // 保存状态2
        System.out.println("Current Content: " + editor.getContent());
​
        // 第三次操作
        editor.type("And this is the third one.");
        // history.save(editor); // 假设这次忘记保存了
        System.out.println("Current Content: " + editor.getContent());
​
​
        // 执行撤销
        history.undo(editor); // 撤销到状态2
        System.out.println("After first undo: " + editor.getContent());
​
        history.undo(editor); // 撤销到状态1
        System.out.println("After second undo: " + editor.getContent());
​
        history.undo(editor); // 没有更多可撤销的了
        System.out.println("After third undo (should be no change or message): " + editor.getContent());
    }
}

在这个例子中:

  • Editor 是 Originator。

  • EditorMemento 是 Memento,作为 Editor 的静态内部类。它的 savedContent 字段和构造函数是私有的(或者包级私有),getSavedContent() 是私有的(理论上),确保只有 Editor 能创建和解释它。

  • History 是 Caretaker,它使用一个 Stack 来存储 EditorMemento 对象,实现了多步撤销。

总结:

备忘录模式是一种强大的行为模式,它通过将对象的状态封装在备忘录中,实现了状态的外部存储和恢复,同时又不破坏对象的封装性。它在需要撤销/重做、历史记录或状态快照的场景中非常有用。关键在于设计好 Memento 的接口,使其对 Originator 开放足够的信息,而对其他对象保持封闭。

相关推荐
bxlj_jcj2 小时前
深入剖析Debezium:CDC领域的“数据魔法棒”
java·架构
叶 落2 小时前
ubuntu 安装 JDK8
java·ubuntu·jdk·安装·java8
爱学习的白杨树2 小时前
Sentinel介绍
java·开发语言
XW2 小时前
java mcp client调用 (modelcontextprotocol)
java·llm
哆啦A梦的口袋呀2 小时前
基于Python学习《Head First设计模式》第十章 状态模式
学习·设计模式
保持学习ing3 小时前
SpringBoot前后台交互 -- 登录功能实现(拦截器+异常捕获器)
java·spring boot·后端·ssm·交互·拦截器·异常捕获器
gadiaola3 小时前
【JVM面试篇】高频八股汇总——类加载和类加载器
java·jvm·面试
七七&5563 小时前
【Java开发日记】基于 Spring Cloud 的微服务架构分析
java·spring cloud·架构
小猫咪怎么会有坏心思呢4 小时前
华为OD机考-数字游戏-逻辑分析(JAVA 2025B卷)
java·游戏·华为od
Aesopcmc4 小时前
idea 启动jar程序并调试
java·intellij-idea·jar