备忘录模式:定义、结构、实现、优缺点、应用场景

大家好,今天我们来聊聊备忘录模式。说实话,这个模式在我刚入行那会儿,总觉得它有点「鸡肋」——不就是保存个状态吗?直到有一次,我在一个文本编辑器项目里,被用户吐槽「撤销功能怎么一撤销就崩了」……嗯,从那以后,我再也不敢小看它了。

什么是备忘录模式?

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

你想想看,我们平时用的「撤销」功能,背后就是备忘录模式在撑腰。它把每一步操作前的状态拍个「快照」,存起来。需要回退时,直接把快照拿出来恢复就行。

模式结构

备忘录模式有三个核心角色,我习惯把它们叫做「三剑客」:

角色 名称 职责
发起人 Originator 需要保存状态的对象,负责创建备忘录和恢复状态
备忘录 Memento 存储发起人内部状态的对象,对外不可修改
管理者 Caretaker 负责保存备忘录,但不能对备忘录内容进行操作

这里有个关键点:管理者不能碰备忘录里的数据。我曾经见过一个项目,管理者直接修改了备忘录的内容,结果整个撤销链全乱了。所以,备忘录对外要「只读」,这是底线。

核心逻辑图

下面这张图,是我自己画的结构示意,你看一眼就能明白它们怎么配合:

发起人 (Originator) - state: 当前状态 + createMemento(): Memento + restore(Memento): void 备忘录 (Memento) - state: 保存的状态 + getState(): State (对外只读) 管理者 (Caretaker) - mementoList: List<Memento> + add(Memento): void + get(index): Memento 创建 恢复 保存/获取

代码实现

咱们直接上代码。我以一个文本编辑器为例,演示备忘录模式的完整实现:

// 1. 备忘录类 —— 保存状态,对外只读
class TextMemento {
    private String content;
    private int cursorPosition;

    public TextMemento(String content, int cursorPosition) {
        this.content = content;
        this.cursorPosition = cursorPosition;
    }

    // 只提供 getter,不提供 setter
    public String getContent() {
        return content;
    }

    public int getCursorPosition() {
        return cursorPosition;
    }
}

// 2. 发起人类 —— 文本编辑器
class TextEditor {
    private String content = "";
    private int cursorPosition = 0;

    public void write(String text) {
        this.content += text;
        this.cursorPosition = this.content.length();
    }

    public void delete(int length) {
        if (length > content.length()) {
            length = content.length();
        }
        this.content = content.substring(0, content.length() - length);
        this.cursorPosition = this.content.length();
    }

    // 创建备忘录
    public TextMemento createMemento() {
        return new TextMemento(content, cursorPosition);
    }

    // 恢复状态
    public void restore(TextMemento memento) {
        this.content = memento.getContent();
        this.cursorPosition = memento.getCursorPosition();
    }

    public void showContent() {
        System.out.println("内容: " + content + " | 光标位置: " + cursorPosition);
    }
}

// 3. 管理者类 —— 管理历史记录
class HistoryManager {
    private List<TextMemento> mementos = new ArrayList<>();

    public void save(TextMemento memento) {
        mementos.add(memento);
    }

    public TextMemento get(int index) {
        if (index < 0 || index >= mementos.size()) {
            return null;
        }
        return mementos.get(index);
    }

    public TextMemento undo() {
        if (mementos.isEmpty()) {
            return null;
        }
        // 移除当前状态,返回上一个状态
        mementos.remove(mementos.size() - 1);
        if (mementos.isEmpty()) {
            return null;
        }
        return mementos.get(mementos.size() - 1);
    }
}

// 4. 使用示例
public class Demo {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        HistoryManager history = new HistoryManager();

        // 第一次写入
        editor.write("Hello");
        history.save(editor.createMemento());
        editor.showContent();  // Hello

        // 第二次写入
        editor.write(" World");
        history.save(editor.createMemento());
        editor.showContent();  // Hello World

        // 撤销一次
        TextMemento previous = history.undo();
        if (previous != null) {
            editor.restore(previous);
        }
        editor.showContent();  // Hello
    }
}

优缺点分析

任何模式都有两面性,备忘录模式也不例外。我根据自己的实战经验,给你列个表:

优点 缺点
1. 保持封装边界,不暴露内部细节 1. 如果状态数据量大,会消耗大量内存
2. 简化发起人职责,状态管理分离 2. 频繁创建备忘录可能影响性能
3. 支持撤销、重做等操作 3. 管理者需要管理备忘录生命周期
4. 实现简单,易于理解 4. 某些语言中,备忘录可能被外部修改

⚠️ 避坑指南

我曾经在一个游戏项目中,把整个游戏场景的状态都存到备忘录里。结果每次保存都产生几十MB的数据,内存直接爆了。后来改用「增量快照」——只记录变化的部分,才解决问题。

所以,如果状态数据很大,别一股脑全存。可以考虑只存差异,或者限制历史记录的数量。

应用场景

备忘录模式在实际项目中,最常见的几个场景:

  • 文本编辑器的撤销/重做 —— 这是最经典的应用,几乎每个编辑器都在用
  • 游戏存档 —— 保存玩家进度,随时可以读档
  • 数据库事务的回滚 —— 在提交前保存快照,出错时回滚
  • 配置管理 —— 修改配置前保存旧值,方便回退
  • 表单填写 —— 用户填到一半,临时保存草稿

💡 我的建议

如果你在做一个需要「后悔药」的功能,备忘录模式就是你的首选。但要注意:别把所有鸡蛋放在一个篮子里。我习惯在备忘录里只保存「核心状态」,那些可以重新计算的数据,就别存了。

总结

备忘录模式,说白了就是给对象拍「快照」。它让状态管理变得清晰,又不会破坏封装。但使用时一定要权衡内存开销,别让「快照」变成「负担」。

嗯,关于备忘录模式,我就讲这么多。记住它的核心思想:保存状态,随时回退。下次你写撤销功能时,不妨试试这个模式。


公众号:蓝海资料掘金营,微信deep3321