Python 备忘录模式(Memento)

Python 中的备忘录模式(Memento Pattern)

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

形象比喻:就像游戏存档——玩家在关键点保存游戏进度(备忘录),随时可以读档恢复到之前的某个状态,而游戏本身不需要暴露所有内部变量。

备忘录模式的三个角色

  1. Originator(原发器):需要保存/恢复状态的对象(如游戏角色、文本编辑器)
  2. Memento(备忘录):存储 Originator 的内部状态(通常不可变,且只允许 Originator 访问)
  3. Caretaker(负责人/管理者):负责保存和管理备忘录(可以有多个存档),但不能修改或查看备忘录内容

优点

  • 保持封装边界:外部无法直接访问对象内部状态
  • 简化原发器:状态管理交给备忘录
  • 支持撤销/重做(Undo/Redo)操作
  • 方便实现快照功能

典型应用场景

  • 文本编辑器的撤销/重做(Ctrl+Z / Ctrl+Y)
  • 游戏存档/读档
  • 图形编辑器的历史状态(Photoshop 的历史面板)
  • 数据库事务回滚
  • 配置管理中的快照恢复

Python 实现示例:带撤销功能的文本编辑器

from typing import List
from copy import deepcopy  # 用于深拷贝复杂状态

# 备忘录(Memento):只存储状态,不提供修改方法
class EditorMemento:
    def __init__(self, text: str, cursor_pos: int):
        self._text = text
        self._cursor_pos = cursor_pos

    # 只允许 Originator 访问状态
    def get_text(self) -> str:
        return self._text

    def get_cursor_pos(self) -> int:
        return self._cursor_pos

    def __str__(self):
        return f"Memento(text='{self._text}', cursor={self._cursor_pos})"

# 原发器(Originator):创建备忘录并恢复状态
class TextEditor:
    def __init__(self):
        self._text = ""
        self._cursor_pos = 0

    def type(self, chars: str):
        # 在光标位置插入文本
        self._text = self._text[:self._cursor_pos] + chars + self._text[self._cursor_pos:]
        self._cursor_pos += len(chars)
        print(f"输入: '{chars}' → 当前: '{self._text}' (光标: {self._cursor_pos})")

    def move_cursor(self, pos: int):
        self._cursor_pos = max(0, min(pos, len(self._text)))
        print(f"移动光标到: {self._cursor_pos}")

    def delete(self, count: int = 1):
        if self._cursor_pos >= count:
            self._text = self._text[:self._cursor_pos - count] + self._text[self._cursor_pos:]
            self._cursor_pos -= count
            print(f"删除 {count} 个字符 → 当前: '{self._text}' (光标: {self._cursor_pos})")

    # 创建备忘录(保存当前状态)
    def save(self) -> EditorMemento:
        print("保存状态...")
        return EditorMemento(self._text, self._cursor_pos)

    # 从备忘录恢复状态
    def restore(self, memento: EditorMemento):
        self._text = memento.get_text()
        self._cursor_pos = memento.get_cursor_pos()
        print(f"恢复状态: '{self._text}' (光标: {self._cursor_pos})")

    def __str__(self):
        return f"文本: '{self._text}' (光标: {self._cursor_pos})"

# 负责人(Caretaker):管理备忘录历史
class History:
    def __init__(self):
        self._history: List[EditorMemento] = []

    def push(self, memento: EditorMemento):
        self._history.append(memento)

    def pop(self) -> EditorMemento:
        if not self._history:
            print("没有可撤销的状态!")
            return None
        return self._history.pop()

    def __len__(self):
        return len(self._history)

# 客户端使用
if __name__ == "__main__":
    editor = TextEditor()
    history = History()

    # 初始保存
    history.push(editor.save())

    editor.type("Hello ")
    history.push(editor.save())

    editor.type("World!")
    history.push(editor.save())

    editor.move_cursor(6)
    editor.type(", Python")
    history.push(editor.save())

    print("\n当前状态:", editor)

    print("\n--- 执行撤销 (Ctrl+Z) ---")
    memento = history.pop()
    if memento:
        editor.restore(memento)

    print("\n--- 再次撤销 ---")
    memento = history.pop()
    if memento:
        editor.restore(memento)

    print("\n--- 再撤销一次 ---")
    memento = history.pop()
    if memento:
        editor.restore(memento)

    print("\n最终状态:", editor)

输出

输入: 'Hello ' → 当前: 'Hello ' (光标: 6)
保存状态...
输入: 'World!' → 当前: 'Hello World!' (光标: 12)
保存状态...
移动光标到: 6
输入: ', Python' → 当前: 'Hello , PythonWorld!' (光标: 15)
保存状态...

当前状态: 文本: 'Hello , PythonWorld!' (光标: 15)

--- 执行撤销 (Ctrl+Z) ---
恢复状态: 'Hello World!' (光标: 12)

--- 再次撤销 ---
恢复状态: 'Hello World!' (光标: 12)

--- 再撤销一次 ---
恢复状态: 'Hello ' (光标: 6)

最终状态: 文本: 'Hello ' (光标: 6)

实现了多级撤销功能!

Pythonic 简化版:使用 deepcopy 或 pickle

如果状态是可序列化的简单对象,可以直接用 deepcopy

import copy

class SimpleEditor:
    def __init__(self):
        self.state = {"text": "", "cursor": 0}
        self.history = []

    def save(self):
        self.history.append(copy.deepcopy(self.state))

    def undo(self):
        if len(self.history) > 1:  # 保留初始状态
            self.history.pop()  # 移除当前
            self.state = copy.deepcopy(self.history[-1])

    def type(self, text):
        self.state["text"] += text
        self.state["cursor"] += len(text)
        print(self.state)

# 使用
editor = SimpleEditor()
editor.save()
editor.type("ABC")
editor.save()
editor.type("DEF")
editor.undo()
# → {'text': 'ABC', 'cursor': 3}

备忘录模式结构总结

角色说明
Originator创建/恢复备忘录的对象(TextEditor)
Memento存储状态的不可变对象(EditorMemento)
Caretaker管理备忘录栈(History)

备忘录模式 vs 命令模式对比(常结合使用)

模式目的是否保存状态典型结合方式
备忘录保存和恢复对象状态与命令模式实现撤销
命令封装操作,支持队列/撤销可选命令执行前保存备忘录

经典组合:命令模式 + 备忘录模式 = 可撤销的操作历史(如上例中的命令管理器)

Python 中的实用建议

  • 简单状态:用 copy.deepcopy()pickle 序列化
  • 复杂状态:自定义 Memento 类,控制访问权限
  • 性能敏感:避免频繁深拷贝大对象(可用增量快照或只存变更)
  • 实际应用
  • undo 库(如 undo 包)
  • 游戏引擎的存档系统
  • Jupyter Notebook 的单元格历史
  • 配置管理工具的快照

注意事项

  • 备忘录应是不可变的(避免外部修改)
  • 大对象频繁保存可能消耗内存(可限制历史长度)
  • 备忘录不应包含 Originator 的引用(避免循环引用)
  • 在多线程环境中需注意线程安全

备忘录模式是实现撤销/重做功能的核心模式,与命令模式结合使用时威力最大。

如果你想看更高级的例子(如多级撤销+重做、游戏角色状态快照、结合命令模式的完整编辑器、增量备忘录优化),或者如何在 GUI 应用中实现,欢迎继续问!

文章已创建 3511

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部