Python 中的备忘录模式(Memento Pattern)
备忘录模式是一种行为型设计模式,其核心目的是:
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样以后就可以将该对象恢复到原先保存的状态。
形象比喻:就像游戏存档——玩家在关键点保存游戏进度(备忘录),随时可以读档恢复到之前的某个状态,而游戏本身不需要暴露所有内部变量。
备忘录模式的三个角色
- Originator(原发器):需要保存/恢复状态的对象(如游戏角色、文本编辑器)
- Memento(备忘录):存储 Originator 的内部状态(通常不可变,且只允许 Originator 访问)
- 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 应用中实现,欢迎继续问!