1. 备忘录模式的定义与目标
1.1 概念与目的
在面向对象设计中 备忘录模式是一种用于保存对象内部状态的结构化方案,便于在以后某个时刻将对象恢复到保存时的状态。核心目标是实现状态回滚而不暴露对象内部实现细节,从而保持 封装性。
通过引入一个独立的 备忘录对象,发起者(Originator)可以在任意时刻把当前状态“快照”存放起来,同时又能在需要时将该快照还原回来。该设计强调状态分离与回滚能力,同时避免让外部组件直接操作发起者的内部数据。
1.2 主要角色与职责
该模式通常包含三个角色:发起者 Originator、备忘录 Memento、以及 管理者 Caretaker。其中,Originator负责创建和恢复状态;Memento承载具体的状态信息;Caretaker保存并管理一组备忘录,以支持多次回滚。'
为了保护封装性,通常会让 Memento 的内部状态对外部不可见,或仅限发起者访问。这种约束在不同语言有不同的实现方式,但目标是一致的:避免外部对象直接修改内部状态。
2. 工作原理与设计要点
2.1 角色协作的基本流程
在典型实现中,Originator在某些关键时刻调用 save(或 saveToMemento、takeSnapshot)方法来产生一个 Memento;随后 Caretaker将该备忘录存储起来以便后续回滚。若需要回滚,Originator 通过 restore 将对象状态恢复到备忘录中的快照。
这一流程的要点在于将状态快照与对象的行为解耦,确保外部对内部状态的影响降到最低。只要发起者具备对快照的读取能力,外部管理者就可完成保存与检索,而不直接操作状态本身。
class Memento:def __init__(self, state):self._state = state # 封装在私有字段中def get_state(self):return self._stateclass Originator:def __init__(self, state):self._state = statedef set_state(self, state):self._state = statedef save(self):return Memento(self._state)def restore(self, memento):self._state = memento.get_state()class Caretaker:def __init__(self):self._mementos = []def add_memento(self, memento):self._mementos.append(memento)def get_memento(self, index):return self._mementos[index]# 示例
originator = Originator("初始状态")
originator.set_state("中间状态")
caretaker = Caretaker()
caretaker.add_memento(originator.save()) # 保存当前快照
originator.set_state("最终状态")
originator.restore(caretaker.get_memento(0)) # 回滚到快照
print(originator._state) # 输出: 初始状态
2.2 封装性与边界条件
封装边界是设计的核心。通常建议将 Memento 的状态设为私有,且仅对 Originator 提供读取入口;Caretaker 只持有对备忘录的引用,而不应能直接修改其内部状态。这样可以最大限度地保证对象内部实现的私密性。
在多线程环境下,并发访问的安全性需要额外关注,通常通过只读快照、锁机制或不可变对象来实现。若采用不可变快照,回滚操作将更加直观且线程安全。

3. 适用场景与限制
3.1 典型使用场景
当系统需要实现“节点回滚”或“状态版本切换”时,备忘录模式提供了清晰的结构与可维护性。典型场景包括:复杂对象的局部修改需要支持撤销、策略选择导致多版本状态的切换,以及需要在运行时保存多份可定制的状态树。 回滚能力通常是其最直接的价值体现。
此外,若对外部状态的暴露必须被严格控制,该模式还能在不暴露内部实现的前提下实现状态管理,保持 API 的简洁与稳定。
3.2 限制与注意事项
该模式并非万能。若对象状态极其庞大、快照成本高、或频繁产生/回滚,快照成本和 内存开销可能成为瓶颈。此时需权衡使用深拷贝、增量快照或仅记录关键字段作为回滚点。
此外,设计复杂度也会因为快照管理而增加,开发者需要清楚地定义哪些状态需要快照、如何版本化以及谁来负责清理过期快照。
4. 不同语言中的实现要点与示例
4.1 语言无关的实现要点
跨语言实现时,关键点在于:定义清晰的接口、将快照与对象状态解耦、保护快照的封装性、以及在需要时提供简洁的回滚机制。适当时可以选择将 Memento 地位提升为内部实现的私有成员,或者通过语言特性实现只读访问。
在设计时还应考虑快照的粒度、可恢复性以及对外部引用的影响,以避免不经意的副作用。
4.2 Python 实现要点与示例
下面的示例展示了一个简洁的三角色实现:Originator、Memento、Caretaker。注意在此处对状态的访问进行了基本封装,以提高可维护性。
class Memento:def __init__(self, state):self._state = statedef get_state(self):return self._stateclass Originator:def __init__(self, state):self._state = statedef set_state(self, state):self._state = statedef save(self):return Memento(self._state)def restore(self, memento):self._state = memento.get_state()class Caretaker:def __init__(self):self._mementos = []def add_memento(self, memento):self._mementos.append(memento)def get_memento(self, index):return self._mementos[index]# 使用示例
originator = Originator("初始状态")
originator.set_state("修改后状态")
caretaker = Caretaker()
caretaker.add_memento(originator.save()) # 保存快照
originator.set_state("再次修改")
originator.restore(caretaker.get_memento(0)) # 回滚到快照
print(originator._state) # 输出: 初始状态
4.3 Java 实现要点与变体
若选择 Java 实现,可以将 Memento 设计为一个独立的类,并通过 Originator 提供创建和恢复的方法,同时让 Caretaker 仅保存对 Memento 的引用。以下给出一个简化版本的类结构示意:
import java.util.ArrayList;
import java.util.List;class Memento {private final String state;Memento(String state) { this.state = state; }String getState() { return state; }
}class Originator {private String state;void setState(String s) { state = s; }String getState() { return state; }Memento saveToMemento() { return new Memento(state); }void restoreFromMemento(Memento m) { state = m.getState(); }
}class Caretaker {private final List mementos = new ArrayList<>();void addMemento(Memento m) { mementos.add(m); }Memento getMemento(int i) { return mementos.get(i); }
}
5. 实践指南与实现要点
5.1 版本化与快照粒度的设计
在设计阶段应明确哪些状态需要被保存为快照,哪些可以通过重建得到。粒度控制直接影响内存消耗与回滚能力;越精简的快照越轻量,但回滚越依赖重建逻辑。
实践中通常采用“只保存必要字段”的策略,并在 Originator 内部维持一个可重建的状态树。这样可以在保持封装性的同时实现高效回滚。
5.2 深拷贝、浅拷贝与不可变对象
快照的拷贝方式直接决定了版本稳定性与性能。对于可变对象,使用深拷贝可以避免后续修改污染快照;但代价较高。若对象天然不可变,不可变对象可以显著简化实现并提升并发安全性。
根据场景选择:若状态包含大量可变子结构,优先考虑增量快照或不可变结构。
5.3 多线程与线程安全
在多线程环境中,CareTaker 的访问需受保护,以防止并发写入导致快照错位。锁机制、只读快照或使用不可变引用都是常见的保障手段。
5.4 与用户交互的撤销/重做设计
如果需要对用户操作提供撤销与重做功能,建议同时维护一个
历史栈,以便实现线性或分支式的回滚策略。合理的界面提示可以帮助用户理解当前状态版本与历史版本之间的区别。


