【HeadFirst系列之HeadFirstJava】第17天之🔄 备忘录模式全解析:如何优雅地实现撤销、回滚与存档?(附 Java 实战)

Scroll Down

Java 设计模式 | 备忘录模式(Memento Pattern)全解析 + 实战

在软件开发中,我们经常需要保存对象的历史状态,并在需要时恢复,例如:

  • 文本编辑器的撤销/重做功能
  • 游戏的存档/回档机制
  • 数据库事务的回滚

如果不加控制地使用对象副本来保存状态,不仅浪费内存,而且增加系统复杂度如何优雅地管理对象的历史状态?—— 备忘录模式(Memento Pattern)登场!

本篇文章基于《Head First 设计模式》,深入剖析 备忘录模式的核心概念、应用场景、优缺点、在 JDK 和 Spring 框架中的应用,并提供 Java 代码实战示例,帮助你掌握这一重要设计模式!🚀


📌 1. 为什么需要备忘录模式?(遇到的问题)

❌ 直接保存对象副本的问题

假设我们要在一个文本编辑器中实现“撤销”功能,最简单的方法是每次修改前保存一份对象副本

EditorState backup = editor.clone();

但这样的问题是:

  1. 增加内存开销:对象副本可能非常大,保存多个状态会占用大量内存。
  2. 破坏封装性:外部代码必须了解 EditorState 的内部细节,违反了 “信息隐藏” 原则。
  3. 难以维护:如果对象状态复杂,直接复制副本的方式会让代码变得臃肿。

👉 如何优雅地管理对象的历史状态,同时保持封装性?—— 备忘录模式登场!


📌 2. 备忘录模式是什么?(核心思想)

备忘录模式(Memento Pattern)用于 保存对象的某个历史状态,并在需要时恢复,而不暴露对象的实现细节

🔹 模式结构

备忘录模式由 三个核心角色 组成:

  1. 发起人(Originator): 需要保存状态的对象,提供创建和恢复备忘录的方法。
  2. 备忘录(Memento): 负责存储发起人的内部状态,保证状态的封装性。
  3. 管理者(Caretaker): 负责存储多个备忘录,并在需要时提供恢复功能。

📌 3. 备忘录模式的 Java 实现(代码实战)

🎯 需求:

我们实现一个文本编辑器,可以保存多个状态,并支持**撤销(Undo)**功能。

🚀 Step 1:定义 Memento(备忘录类)

// 备忘录类:存储 Editor 的状态
class Memento {
    private final String text;

    public Memento(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }
}

🚀 Step 2:定义 Editor(发起人)

// 发起人:文本编辑器,创建和恢复备忘录
class Editor {
    private String text = "";

    public void type(String words) {
        text += words;
    }

    public String getText() {
        return text;
    }

    // 创建备忘录
    public Memento save() {
        return new Memento(text);
    }

    // 恢复备忘录
    public void restore(Memento memento) {
        this.text = memento.getText();
    }
}

🚀 Step 3:定义 Caretaker(管理者)

import java.util.Stack;

// 管理者:负责存储和恢复备忘录
class Caretaker {
    private Stack<Memento> history = new Stack<>();

    public void saveState(Editor editor) {
        history.push(editor.save());
    }

    public void undo(Editor editor) {
        if (!history.isEmpty()) {
            editor.restore(history.pop());
        }
    }
}

🚀 Step 4:测试备忘录模式

public class MementoPatternDemo {
    public static void main(String[] args) {
        Editor editor = new Editor();
        Caretaker caretaker = new Caretaker();

        // 输入文字
        editor.type("Hello, ");
        caretaker.saveState(editor);

        editor.type("World!");
        caretaker.saveState(editor);

        System.out.println("当前内容:" + editor.getText()); // Hello, World!

        // 撤销一次
        caretaker.undo(editor);
        System.out.println("撤销一次:" + editor.getText()); // Hello, 

        // 再次撤销
        caretaker.undo(editor);
        System.out.println("撤销两次:" + editor.getText()); // (空)
    }
}

📌 运行结果:

当前内容:Hello, World!
撤销一次:Hello, 
撤销两次:

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

适用于:

  • 文本编辑器的撤销/重做(如 Word、IDE 代码编辑器)
  • 游戏存档/回档(如 RPG 游戏)
  • 数据库事务回滚(如 Hibernate 的 savepoint 机制)
  • Web 浏览器的历史记录

不适用于:

  • 对象状态过于庞大(可能消耗大量内存)
  • 状态变化过于频繁(存储和恢复的成本较高)

📌 5. 备忘录模式在 JDK & Spring 中的应用

🔹 JDK 中的应用

  • Java 的 Serializable 接口 允许对象序列化成字节流,从而保存和恢复状态。
  • java.util.Stack 作为 Caretaker,可以存储多个状态,实现撤销功能。

🔹 Spring 框架中的应用

  • Spring 事务管理(Transaction Management)
    • Spring 通过 TransactionManager 维护数据库的回滚点,类似于备忘录模式中的 Caretaker
    • 事务的 savepoint 机制允许在事务中存储多个回滚点,并在异常发生时恢复

📌 6. 备忘录模式的优缺点

✅ 优点

封装性好:状态存储在 Memento 中,不影响 Originator 的实现细节。
支持撤销/回滚:可以轻松地恢复历史状态。
解耦管理者与发起人Caretaker 仅管理 Memento,不需要了解 Originator 细节。

❌ 缺点

可能消耗大量内存:如果对象状态过大,存储多个 Memento 可能导致内存占用过高。
状态变化过快时效率低:如果状态变化频繁,存储和恢复 Memento 的操作可能会影响性能。


📌 7. 总结

  • 备忘录模式适用于需要保存和恢复历史状态的场景,如撤销、游戏存档、事务回滚等。
  • 它封装了对象状态,保持封装性,避免直接暴露内部数据。
  • 在 JDK 和 Spring 事务管理中都有类似应用,值得深入理解和运用!

💡 你是否在项目中用过类似的设计?欢迎留言交流!🚀