备忘录模式(Memento Pattern)是一种行为型设计模式,它允许在不破坏封装性的前提下,捕获和保存对象的内部状态,并在需要时恢复该状态。这种模式非常适合实现撤销(Undo)功能,或者需要保存对象历史状态的场景。

备忘录模式的核心角色

  1. 原发器(Originator):需要保存状态的对象,能够创建备忘录和从备忘录恢复状态
  2. 备忘录(Memento):存储原发器的内部状态,防止原发器以外的对象访问其内容
  3. 负责人(Caretaker):负责保存备忘录,但不能对备忘录的内容进行操作或检查

备忘录模式的实现示例

下面以"文本编辑器"为例展示备忘录模式的实现,编辑器可以保存当前文本状态,支持撤销操作回到之前的状态:

#include <iostream>
#include <string>
#include <vector>
#include <memory>

// 前向声明
class TextEditor;

// 备忘录:存储文本编辑器的状态
class Memento {
private:
    // 只有TextEditor可以访问备忘录的内容(友元)
    friend class TextEditor;
    
    std::string text;      // 文本内容
    int cursorPosition;    // 光标位置
    
    // 私有构造函数,防止外部创建
    Memento(const std::string& txt, int pos) 
        : text(txt), cursorPosition(pos) {}
};

// 原发器:文本编辑器
class TextEditor {
private:
    std::string text;
    int cursorPosition;

public:
    TextEditor() : cursorPosition(0) {}

    // 设置文本
    void setText(const std::string& newText) {
        text = newText;
        cursorPosition = text.length();
    }

    // 添加文本
    void addText(const std::string& addedText) {
        text.insert(cursorPosition, addedText);
        cursorPosition += addedText.length();
    }

    // 移动光标
    void moveCursor(int position) {
        if (position >= 0 && position <= static_cast<int>(text.length())) {
            cursorPosition = position;
        }
    }

    // 创建备忘录,保存当前状态
    std::unique_ptr<Memento> createMemento() const {
        return std::make_unique<Memento>(text, cursorPosition);
    }

    // 从备忘录恢复状态
    void restoreFromMemento(const Memento* memento) {
        if (memento) {
            text = memento->text;
            cursorPosition = memento->cursorPosition;
        }
    }

    // 显示当前状态
    void display() const {
        std::cout << "当前文本: " << text << std::endl;
        std::cout << "光标位置: " << cursorPosition << std::endl;
    }
};

// 负责人:管理备忘录(历史记录)
class Caretaker {
private:
    std::vector<std::unique_ptr<Memento>> history;
    int currentIndex;  // 当前状态索引

public:
    Caretaker() : currentIndex(-1) {}

    // 保存状态
    void saveState(TextEditor* editor) {
        // 清除当前状态之后的历史(如多次撤销后再操作)
        if (currentIndex < static_cast<int>(history.size()) - 1) {
            history.erase(history.begin() + currentIndex + 1, history.end());
        }
        
        // 保存新状态
        history.push_back(editor->createMemento());
        currentIndex = history.size() - 1;
    }

    // 撤销操作
    bool undo(TextEditor* editor) {
        if (currentIndex > 0) {
            currentIndex--;
            editor->restoreFromMemento(history[currentIndex].get());
            return true;
        }
        return false;  // 已经是最早状态
    }

    // 重做操作
    bool redo(TextEditor* editor) {
        if (currentIndex < static_cast<int>(history.size()) - 1) {
            currentIndex++;
            editor->restoreFromMemento(history[currentIndex].get());
            return true;
        }
        return false;  // 已经是最新状态
    }
};

// 客户端使用
int main() {
    TextEditor editor;
    Caretaker caretaker;

    // 初始状态
    editor.setText("Hello, ");
    caretaker.saveState(&editor);
    std::cout << "=== 初始状态 ===" << std::endl;
    editor.display();

    // 第一次修改
    editor.addText("World!");
    caretaker.saveState(&editor);
    std::cout << "\n=== 第一次修改后 ===" << std::endl;
    editor.display();

    // 第二次修改
    editor.moveCursor(7);
    editor.addText("beautiful ");
    caretaker.saveState(&editor);
    std::cout << "\n=== 第二次修改后 ===" << std::endl;
    editor.display();

    // 撤销一次
    if (caretaker.undo(&editor)) {
        std::cout << "\n=== 撤销一次后 ===" << std::endl;
        editor.display();
    }

    // 再撤销一次
    if (caretaker.undo(&editor)) {
        std::cout << "\n=== 再撤销一次后 ===" << std::endl;
        editor.display();
    }

    // 重做一次
    if (caretaker.redo(&editor)) {
        std::cout << "\n=== 重做一次后 ===" << std::endl;
        editor.display();
    }

    return 0;
}

备忘录模式的工作原理

  1. 原发器创建一个备忘录,用于保存自身当前的内部状态
  2. 负责人存储这个备忘录,但不能查看或修改备忘录的内容
  3. 当需要恢复状态时,原发器从负责人那里获取备忘录,并使用其中的数据恢复自身状态
  4. 备忘录的内容只能由原发器访问,确保了封装性

备忘录模式的两种实现方式

  • 窄接口:备忘录只提供有限的接口(如示例中通过友元实现),只有原发器能访问其内部状态
  • 宽接口:备忘录提供较宽的接口,允许其他对象访问其内部状态,这种方式可能破坏封装性

备忘录模式与命令模式的区别

  • 备忘录模式:专注于保存和恢复对象的状态,主要用于实现撤销功能
  • 命令模式:专注于封装请求,除了撤销外,还支持命令排队、日志记录等

备忘录模式的应用场景

  1. 需要实现撤销/重做功能的场景(如文本编辑器、图形编辑器)
  2. 需要保存对象历史状态,以便后续分析或恢复的场景
  3. 需要在不暴露对象内部结构的前提下,捕获和恢复对象状态的场景

备忘录模式的优缺点

优点

  • 实现了状态的封装,保存和恢复状态不会破坏对象的封装性
  • 提供了状态恢复机制,支持撤销操作
  • 负责人与原发器解耦,负责人无需了解状态的细节

缺点

  • 如果对象状态较大,频繁创建备忘录会消耗较多内存
  • 每保存一次状态都需要创建一个新的备忘录对象,可能影响性能
  • 如果原发器的状态变化频繁且复杂,备忘录的管理会变得困难

备忘录模式在许多应用程序中都有广泛应用,例如:

  • 文本编辑器的撤销/重做功能
  • 图形设计软件中的历史记录功能
  • 数据库事务的回滚机制
  • 游戏中的存档和读档功能

这种模式通过巧妙的封装,既实现了状态的保存与恢复,又不破坏对象的封装性,是实现撤销功能的首选模式。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐