python设计模式–备忘录模式(Memento Pattern)

Python 设计模式——备忘录模式(Memento Pattern)详解

本文面向零基础新手,从概念到“状态快照与恢复”,从简单示例到编辑器撤销,全方位讲解备忘录模式。


一、什么是备忘录模式?

1.1 通俗理解

备忘录模式是一种行为型设计模式,核心思想是:

在不破坏封装的前提下,把对象的“当前状态”保存成一份“备忘录”(快照);以后需要时,可以用这份备忘录把对象恢复到当时的状态。负责保存和恢复的“管理者”只持有备忘录对象,不直接接触对象内部细节,这样既支持撤销/存档,又不会让外部随意修改对象内部。

可以这样类比:

  • 游戏存档:当前角色血量、位置、背包是一个复杂状态;存档 = 把这份状态“拍一张快照”存起来;读档 = 用快照把角色恢复成当时的样子。快照(备忘录)可以交给“存档管理器”保存到文件,管理器不需要知道状态里每个字段是什么意思,只要保管好快照即可。
  • 编辑器撤销:每输入一段文字,编辑器就把“当前全文”保存成一个备忘录放进历史;点撤销时,从历史里取出上一个备忘录,把编辑器恢复成那个状态。备忘录只负责“存状态”,恢复时由编辑器自己根据备忘录还原。

所以,备忘录模式解决的是:需要“保存状态、稍后恢复”(撤销、存档、快照)时,把状态封装成备忘录,由管理者保管,由原对象自己负责“生成备忘录”和“从备忘录恢复”,从而不暴露内部结构。

1.2 为什么需要备忘录模式?

场景一:撤销(Undo)

编辑器、表单、配置界面等,用户操作后要能撤销。若把状态直接暴露给“历史记录”,历史记录就要依赖对象内部结构,耦合高且易出错。用备忘录:对象在关键点生成“状态快照”(备忘录),历史只存备忘录;撤销时把备忘录交回对象,由对象自己恢复,外部不碰内部字段

场景二:存档 / 读档

游戏、工作流等要保存进度。把“当前状态”序列化成备忘录(或再存到文件),需要时反序列化后交给对象恢复。管理者(存档管理器)只负责存/取备忘录,不解析状态内容。

场景三:快照 / 回滚

配置、数据库等要做“某一时刻的快照”,之后可回滚。快照内容就是备忘录;回滚时用备忘录恢复,不破坏对象封装。

1.3 适用场景(什么时候用?)

场景 说明
撤销 / 重做 保存历史状态,支持回退或重做。
存档 / 读档 保存进度,之后恢复。
快照 / 回滚 某一时刻的状态备份与恢复。

简单记忆:需要把对象状态保存下来、以后能恢复,又不想让外部直接操作对象内部时,用备忘录模式。


二、备忘录模式的结构(三个角色)

角色 说明
原发器(Originator) 拥有需要保存的“内部状态”;提供 create_memento() 生成当前状态的备忘录,提供 restore(memento) 从备忘录恢复状态。只有原发器知道备忘录里存了什么、怎么恢复。
备忘录(Memento) 存放原发器在某一时刻的状态快照;对外可以“不透明”(管理者只存、取,不解析内容),或只暴露原发器需要的接口,避免其他对象修改快照。
管理者(Caretaker) 负责保存备忘录(如放进列表做历史、写入文件);在需要恢复时把备忘录交还给原发器,由原发器 restore(memento)。管理者不修改备忘录内容,也不依赖原发器内部结构。

关系可以理解为:

  • 原发器在关键点(如每次编辑后)调用 create_memento(),得到备忘录并交给管理者
  • 需要恢复时,管理者把某个备忘录还给原发器,原发器 restore(memento) 把自己的状态还原。
  • 备忘录里具体存什么(字典、序列化字符串、私有属性),由原发器决定;管理者只负责“保管”,不解析。

三、示例一:简单计数器 + 撤销(最简版)

需求:一个计数器,有 value;可以 increase(),也可以“撤销”到上一步。用备忘录保存每次操作前的 value,撤销时恢复。

3.1 备忘录:只存一个数字(状态快照)

class Memento:
    """备忘录:只保存 value 的快照,对外可只读"""
    def __init__(self, value: int):
        self._value = value

    def get_state(self):
        return self._value

3.2 原发器:计数器(创建备忘录 + 恢复)

class Counter:
    """原发器:拥有状态,能创建备忘录并能从备忘录恢复"""
    def __init__(self):
        self.value = 0

    def increase(self):
        self.value += 1

    def create_memento(self) -> Memento:
        return Memento(self.value)

    def restore(self, memento: Memento):
        self.value = memento.get_state()

3.3 管理者:保存历史备忘录

class Caretaker:
    """管理者:只保存备忘录,不解析内容;需要时交还原发器"""
    def __init__(self):
        self._history = []

    def save(self, memento: Memento):
        self._history.append(memento)

    def undo(self) -> Memento:
        if not self._history:
            return None
        return self._history.pop()

3.4 使用

counter = Counter()
caretaker = Caretaker()

counter.increase()  # 1
caretaker.save(counter.create_memento())
counter.increase()  # 2
caretaker.save(counter.create_memento())
counter.increase()  # 3
print(counter.value)  # 3

m = caretaker.undo()
if m:
    counter.restore(m)
    print(counter.value)  # 2
m = caretaker.undo()
if m:
    counter.restore(m)
    print(counter.value)  # 1

要点:状态只存在 Memento 里,由 Counter 自己写入和读出;Caretaker 只做“存”和“取”,不关心 value 是多少。


四、示例二:文本编辑器(保存/恢复多字段)

需求:编辑器有 text 和 cursor_pos;支持“保存快照”和“从快照恢复”,用于撤销。

4.1 备忘录:存文本和光标(可做成不透明)

class EditorMemento:
    """备忘录:存编辑器某时刻的 text 和 cursor_pos"""
    def __init__(self, text: str, cursor_pos: int):
        self._text = text
        self._cursor_pos = cursor_pos

    def get_text(self):
        return self._text

    def get_cursor_pos(self):
        return self._cursor_pos

4.2 原发器:编辑器

class Editor:
    def __init__(self):
        self.text = ""
        self.cursor_pos = 0

    def type_char(self, c: str):
        self.text = self.text[:self.cursor_pos] + c + self.text[self.cursor_pos:]
        self.cursor_pos += 1

    def create_memento(self) -> EditorMemento:
        return EditorMemento(self.text, self.cursor_pos)

    def restore(self, m: EditorMemento):
        self.text = m.get_text()
        self.cursor_pos = m.get_cursor_pos()

4.3 管理者 + 使用

class History:
    def __init__(self):
        self._stack = []

    def push(self, memento):
        self._stack.append(memento)

    def pop(self):
        return self._stack.pop() if self._stack else None

editor = Editor()
history = History()
history.push(editor.create_memento())  # 空状态

editor.type_char("a")
editor.type_char("b")
history.push(editor.create_memento())
editor.type_char("c")
print(editor.text)  # abc

m = history.pop()
editor.restore(m)
print(editor.text)  # ab

要点:多字段(text、cursor_pos)都放进备忘录;恢复时由 Editor 自己从备忘录读出来写回自己,管理者不解析。


五、示例三:备忘录“不透明”(管理者不解析内容)

若希望管理者完全不能看到或修改备忘录内容,可以让备忘录只暴露给原发器:例如备忘录和原发器在同一模块,备忘录的“取状态”方法只允许原发器调用,或备忘录用私有属性存储,不提供 getter。Python 里可以用约定:备忘录类只由原发器创建和读取,管理者只持有引用。下面用“原发器把状态序列化成字典、备忘录只存字典”的方式,管理者拿到的只是“一个盒子”,不解析:

class Memento:
    """不透明备忘录:只存一个 dict,不提供按字段访问"""
    def __init__(self, state: dict):
        self._state = state.copy()

    def get_state_for_originator(self):
        """仅原发器应调用,用于恢复"""
        return self._state.copy()

class Originator:
    def __init__(self):
        self.x = 0
        self.y = 0

    def create_memento(self) -> Memento:
        return Memento({"x": self.x, "y": self.y})

    def restore(self, m: Memento):
        state = m.get_state_for_originator()
        self.x = state["x"]
        self.y = state["y"]

管理者只做 caretaker.save(originator.create_memento())m = caretaker.undo(); originator.restore(m),不调用 get_state_for_originator,即不解析内容,实现“不透明”。


六、备忘录模式 vs 其他

模式 目的 与备忘录的区别
备忘录 保存与恢复对象状态,不破坏封装 强调“快照”“恢复”“管理者不解析”。
命令 + 撤销 用命令对象记录操作,撤销时执行反向操作 命令模式撤销靠“反向操作”;备忘录靠“恢复旧状态”。可结合:命令里存一个 memento,撤销时 restore。
原型 克隆对象得到副本 克隆是“复制当前对象”;备忘录是“存一份状态、以后恢复”,通常不保留对象引用。

简单记忆:备忘录 = 状态快照 + 原发器自己写/读 + 管理者只保管


七、常见问题与注意点

7.1 备忘录里存什么?

存“恢复所需的最小状态”。可以是原发器所有相关字段的拷贝,或序列化后的字符串/字典;只有原发器知道如何生成和解析,管理者不依赖这些字段含义。

7.2 历史很多时会不会占内存?

会。可以限制历史条数(如只保留最近 20 条),或把旧备忘录持久化到磁盘,需要时再加载;备忘录模式本身不限制“存多少、存哪”,由管理者策略决定。

7.3 原发器变了(新增字段),旧备忘录怎么兼容?

若备忘录里是“字段名 -> 值”的字典,恢复时可以对缺失的键给默认值;若备忘录是序列化字节,可能需要版本号或兼容性策略。这是设计时的取舍问题。


八、小结

  • 备忘录模式:在不破坏封装的前提下,把对象的状态保存成备忘录(快照);原发器负责创建备忘录和从备忘录恢复,管理者只负责保存和交出备忘录,不解析内容,从而支持撤销、存档、快照
  • 三个角色:原发器(有状态,create_memento / restore)、备忘录(存状态,可对管理者不透明)、管理者(保存/取出备忘录)。
  • 典型用途:编辑器撤销、游戏存档、配置快照与回滚等。
  • 实现要点:备忘录只暴露原发器需要的信息(或完全不暴露);恢复逻辑只在原发器里;管理者不修改、不解析备忘录。

建议先写“计数器 + 撤销”,再写“编辑器 text/cursor 快照与恢复”,体会“状态打包成备忘录、管理者只保管”,这样对备忘录模式会掌握得比较扎实。

发表评论