📕目录

class 卑微码农:
def __init__(self):
self.技能 = ['能读懂十年前祖传代码', '擅长用Ctrl+C/V搭建世界', '信奉"能跑就别动"的玄学']
self.发量 = 100 # 初始发量
self.咖啡因耐受度 = '极限'
def 修Bug(self, bug):
try:
# 试图用玄学解决问题
if bug.严重程度 == '离谱':
print("这一定是环境问题!")
else:
print("让我看看是谁又没写注释...哦,是我自己。")
except Exception as e:
# 如果try块都救不了,那就...
print("重启一下试试?")
self.发量 -= 1 # 每解决一个bug,头发-1
# 实例化一个我
我 = 卑微码农()
前言

“刚写完的代码不小心覆盖了?”“PS 修图到一半手滑删了关键图层?”“游戏打 BOSS 前忘了存档,团灭后只能从头再来?” 这些让人崩溃的场景,本质上都指向同一个需求 —— 对象状态的 “时光回溯” 能力。而在设计模式的世界里,专门解决这个问题的 “救场神器”,就是我们今天的主角 —— 备忘录模式。
作为行为型设计模式的重要成员,备忘录模式不仅是 IDE、文本编辑器、图形软件等工具 “撤销 / 重做” 功能的核心骨架,还广泛应用于游戏存档、数据库事务回滚、配置版本管理等场景。本文将彻底抛开晦涩的理论堆砌,用 C++ 代码手把手带你从 0 到 1 实现备忘录模式,从基础用法讲到工程化优化,再到真实场景落地,让你既能理解 “是什么”,更能掌握 “怎么用”,还能避开实际开发中的坑。
一、先搞懂:备忘录模式到底解决什么问题?

在讲定义之前,我们先看一个真实的开发场景。假设你正在开发一个简单的文本编辑器,核心需求是 “支持撤销操作”—— 用户输入一段文字后,能随时退回到上一步的内容状态。
如果不用设计模式,你可能会这么做:给编辑器类加一个字符串变量存储上一步内容,每次修改前先把当前内容存进去。但这样的问题很明显:
- 只能撤销一次,无法支持多步撤销;
- 状态存储逻辑和编辑器的核心编辑功能混在一起,代码越改越乱(比如后续加 “重做” 就要动核心代码);
- 暴露了对象的内部状态,比如其他类可能不小心修改了存储的历史内容,导致状态混乱。
而备忘录模式的核心思想,就是 “在不破坏对象封装性的前提下,捕获对象的内部状态并外部化存储,以便后续恢复”。简单说,就是把对象的 “快照” 存到外面,既不暴露对象的内部细节,又能随时读回快照恢复状态。
用生活中的例子类比:就像你写论文时,每完成一个章节就保存一个版本(论文 V1、V2、V3),这些版本文件就是 “备忘录”;你自己是 “发起人”(负责生成内容和使用版本文件);而存放这些版本的文件夹就是 “管理者”(只负责保管,不修改内容)。当你误删内容时,直接从文件夹里找到对应版本打开即可恢复 —— 既不用记住之前写了什么(不暴露内部状态),又能灵活回滚(外部化存储)。
二、拆解核心:备忘录模式的三个角色

备忘录模式的结构非常清晰,无论多么复杂的应用场景,都离不开三个核心角色的配合。我们结合 UML 图和角色职责,把它们彻底讲透,避免后续写代码时混淆。
2.1 核心角色分工
备忘录模式的三个角色各司其职,形成 “状态创建 - 存储 - 恢复” 的闭环,同时严格遵守 “封装隔离” 原则 —— 这也是它能解决 “暴露内部状态” 问题的关键。
| 角色名称 | 核心职责 | 类比(论文场景) |
|---|---|---|
| Originator(发起人) | 1. 维护需要保存的内部状态;2. 提供创建备忘录(快照)的方法;3. 提供从备忘录恢复状态的方法。 | 写论文的你,负责生成论文内容,也能打开历史版本修改。 |
| Memento(备忘录) | 1. 存储发起人的内部状态;2. 仅向发起人暴露状态访问接口,对其他角色隐藏细节。 | 论文的历史版本文件(V1.docx),只允许你查看和修改,别人不能动。 |
| Caretaker(管理者) | 1. 负责存储和管理备忘录;2. 不操作备忘录的内部状态,仅作为 “容器”。 | 存放论文版本的文件夹,只负责保管文件,不修改文件内容。 |
2.2 角色交互 UML 图
下面的 UML 图清晰展示了三个角色的交互流程:发起人创建备忘录后,由管理者负责存储;恢复时,管理者将备忘录交还给发起人,由发起人完成状态恢复。
class Originator {
- state: string
+ createMemento(): Memento
+ restoreMemento(m: Memento): void
+ setState(s: string): void
+ getState(): string
}
class Memento {
- state: string
+ Memento(s: string) // 私有构造(实际代码中通过友元实现)
+ getState(): string // 私有访问(实际代码中通过友元实现)
}
class Caretaker {
- mementoList: List<Memento>
+ addMemento(m: Memento): void
+ getMemento(index: int): Memento
+ getLatestMemento(): Memento
}
Originator "1" -- "创建" Memento:
Caretaker "1" -- "管理" Memento:
Originator "1" -- "从...恢复" Memento:
这里有个关键原则必须记住:备忘录的状态只能由发起人访问。管理者和其他角色都不能修改备忘录的内容,这就保证了状态的安全性和一致性 —— 就像只有你能打开自己的论文版本,别人不能随便改一样。
三、动手实践:C++ 实现文本编辑器的撤销 / 重做功能

理论讲完,我们立刻进入实战。以 “支持多步撤销和重做的文本编辑器” 为目标,用 C++ 实现备忘录模式的完整流程。这次我们会避开 “玩具代码”,加入异常处理、封装优化、边界判断等工程化细节,确保代码能直接用到实际项目中。
3.1 第一步:定义备忘录类(Memento)
备忘录的核心是 “安全存储状态”,所以我们要通过 C++ 的访问控制(private+friend)保证状态只能被发起人访问,避免外部类篡改。
#include <string>
// 前向声明:让备忘录知道发起人的存在(后续友元声明需要)
class TextEditor;
// 备忘录类:存储文本编辑器的状态(仅对发起人开放访问)
class TextMemento {
private:
// 1. 存储的核心状态:文本内容
std::string content_;
// 2. 私有构造函数:仅允许发起人创建备忘录(防止外部随意实例化)
TextMemento(const std::string& content) : content_(content) {}
// 3. 私有状态访问接口:仅允许发起人获取状态(防止外部篡改)
std::string getContent() const {
return content_;
}
// 4. 友元声明:将发起人设为友元,突破访问限制(核心!)
friend class TextEditor;
};
关键设计说明:
- 私有构造 + 友元:确保只有
TextEditor(发起人)能创建备忘录,避免外部类创建无效的备忘录; - 私有
getContent():只有发起人能读取状态,其他类(比如管理者)只能 “保管” 备忘录,不能查看或修改内容,完美遵守封装原则。
3.2 第二步:定义发起人(TextEditor)
发起人是业务核心,负责文本编辑(追加、替换、清空)、创建备忘录(保存状态)和从备忘录恢复状态。我们会把业务逻辑和状态管理逻辑分开,符合 “单一职责原则”。
#include <iostream>
#include <string>
// 发起人:文本编辑器(核心业务类)
class TextEditor {
private:
// 发起人自身的状态:当前文本内容
std::string current_content_;
public:
// 构造函数:初始化空文本
TextEditor() : current_content_("") {}
// --------------- 核心业务操作:文本编辑 ---------------
// 1. 追加文本
void appendText(const std::string& text) {
if (text.empty()) {
std::cout << "警告:追加的文本为空!" << std::endl;
return;
}
current_content_ += text;
std::cout << "[编辑] 已追加文本,当前内容:" << current_content_ << std::endl;
}
// 2. 替换文本(覆盖当前内容)
void replaceText(const std::string& text) {
if (text.empty()) {
std::cout << "警告:替换的文本为空!" << std::endl;
return;
}
current_content_ = text;
std::cout << "[编辑] 已替换文本,当前内容:" << current_content_ << std::endl;
}
// 3. 清空文本
void clearText() {
current_content_.clear();
std::cout << "[编辑] 已清空文本,当前内容为空" << std::endl;
}
// 4. 显示当前内容
void showContent() const {
std::cout << "[状态] 当前文本内容:" << current_content_ << std::endl;
}
// --------------- 备忘录相关操作:状态管理 ---------------
// 1. 创建备忘录(保存当前状态)
TextMemento createMemento() const {
// 调用备忘录的私有构造函数,创建状态快照
return TextMemento(current_content_);
}
// 2. 从备忘录恢复状态
void restoreFromMemento(const TextMemento& memento) {
// 调用备忘录的私有接口获取状态,恢复自身状态
current_content_ = memento.getContent();
std::cout << "[恢复] 已恢复到历史状态,当前内容:" << current_content_ << std::endl;
}
};
关键设计说明:
- 业务与状态分离:文本编辑(
appendText/replaceText)和状态管理(createMemento/restoreFromMemento)分开实现,后续修改编辑逻辑不会影响状态管理,反之亦然; - 边界判断:对空文本的追加 / 替换做了警告处理,避免无效操作,这是工程化代码的基本要求。
3.3 第三步:定义管理者(Caretaker)
管理者的作用是 “容器”,负责存储多个备忘录以支持多步撤销 / 重做。我们用两个 vector 分别存储 “撤销历史” 和 “重做历史”,并加入异常处理避免访问越界,让功能更健壮。
#include <vector>
#include <stdexcept>
#include <iostream>
// 管理者:备忘录的存储和管理(不触碰任何状态细节)
class MementoCaretaker {
private:
// 存储撤销历史:保存所有已创建的备忘录(支持多步撤销)
std::vector<TextMemento> undo_stack_;
// 存储重做历史:保存被撤销的备忘录(支持多步重做)
std::vector<TextMemento> redo_stack_;
public:
// 1. 保存备忘录(编辑操作时调用,清空重做历史)
void saveMemento(const TextMemento& memento) {
undo_stack_.push_back(memento);
redo_stack_.clear(); // 新操作后,重做历史失效
std::cout << "[管理] 已保存状态,当前可撤销步数:" << undo_stack_.size() << std::endl;
}
// 2. 撤销操作:获取最新的备忘录,并存入重做历史
TextMemento undo() {
if (undo_stack_.empty()) {
throw std::runtime_error("撤销失败:没有历史状态可恢复!");
}
// 取出最后一个状态(最新的历史)
TextMemento latest = undo_stack_.back();
undo_stack_.pop_back();
// 存入重做栈,支持后续重做
redo_stack_.push_back(latest);
std::cout << "[管理] 执行撤销,剩余可撤销步数:" << undo_stack_.size() << std::endl;
return latest;
}
// 3. 重做操作:获取最近被撤销的备忘录,并存回撤销历史
TextMemento redo() {
if (redo_stack_.empty()) {
throw std::runtime_error("重做失败:没有可重做的状态!");
}
// 取出最后一个被撤销的状态
TextMemento latest = redo_stack_.back();
redo_stack_.pop_back();
// 存回撤销栈,支持再次撤销
undo_stack_.push_back(latest);
std::cout << "[管理] 执行重做,剩余可重做步数:" << redo_stack_.size() << std::endl;
return latest;
}
// 4. 清空所有历史状态
void clearAll() {
undo_stack_.clear();
redo_stack_.clear();
std::cout << "[管理] 已清空所有历史状态" << std::endl;
}
};
关键设计说明:
- 双栈设计:
undo_stack_存撤销历史,redo_stack_存重做历史,完美支持 “撤销 - 重做” 的闭环(比如撤销后再编辑,重做历史会清空,符合用户习惯); - 异常处理:当无撤销 / 重做状态时抛出异常,避免程序崩溃,后续客户端可以通过 try-catch 处理;
- 不碰状态细节:管理者只存储和传递备忘录对象,从不调用
getContent()等状态接口,严格遵守 “只保管不修改” 的原则。
3.4 第四步:客户端测试代码(完整可运行)
我们模拟用户的真实操作流程:多次编辑文本→保存状态→执行多步撤销→执行多步重做→再次编辑,验证功能的完整性和健壮性。代码中加入 try-catch 块处理异常,让程序更稳定。
int main() {
// 初始化三个核心角色
TextEditor editor;
MementoCaretaker caretaker;
try {
std::cout << "==================== 第一次编辑 ====================" << std::endl;
editor.appendText("备忘录模式详解:");
caretaker.saveMemento(editor.createMemento()); // 保存状态1:"备忘录模式详解:"
std::cout << "\n==================== 第二次编辑 ====================" << std::endl;
editor.appendText("一种用于状态恢复的设计模式");
caretaker.saveMemento(editor.createMemento()); // 保存状态2:"备忘录模式详解:一种用于状态恢复的设计模式"
std::cout << "\n==================== 第三次编辑(错误操作) ====================" << std::endl;
editor.replaceText("这是一段错误的内容"); // 不保存状态
editor.showContent(); // 显示错误内容
std::cout << "\n==================== 执行撤销(回到状态2) ====================" << std::endl;
editor.restoreFromMemento(caretaker.undo());
editor.showContent();
std::cout << "\n==================== 再次撤销(回到状态1) ====================" << std::endl;
editor.restoreFromMemento(caretaker.undo());
editor.showContent();
std::cout << "\n==================== 执行重做(回到状态2) ====================" << std::endl;
editor.restoreFromMemento(caretaker.redo());
editor.showContent();
std::cout << "\n==================== 继续编辑 ====================" << std::endl;
editor.appendText(",广泛应用于撤销/重做功能");
caretaker.saveMemento(editor.createMemento()); // 保存状态3:新增内容后
editor.showContent();
std::cout << "\n==================== 执行重做(已无可用状态) ====================" << std::endl;
editor.restoreFromMemento(caretaker.redo()); // 此时重做栈为空,会抛出异常
}
catch (const std::exception& e) {
std::cout << "[异常] " << e.what() << std::endl;
}
std::cout << "\n==================== 清空所有状态 ====================" << std::endl;
caretaker.clearAll();
editor.showContent();
return 0;
}
3.5 运行结果与分析
编译运行上述代码(支持 C++11 及以上标准),输出如下:
==================== 第一次编辑 ====================
[编辑] 已追加文本,当前内容:备忘录模式详解:
[管理] 已保存状态,当前可撤销步数:1
==================== 第二次编辑 ====================
[编辑] 已追加文本,当前内容:备忘录模式详解:一种用于状态恢复的设计模式
[管理] 已保存状态,当前可撤销步数:2
==================== 第三次编辑(错误操作) ====================
[编辑] 已替换文本,当前内容:这是一段错误的内容
[状态] 当前文本内容:这是一段错误的内容
==================== 执行撤销(回到状态2) ====================
[管理] 执行撤销,剩余可撤销步数:1
[恢复] 已恢复到历史状态,当前内容:备忘录模式详解:一种用于状态恢复的设计模式
[状态] 当前文本内容:备忘录模式详解:一种用于状态恢复的设计模式
==================== 再次撤销(回到状态1) ====================
[管理] 执行撤销,剩余可撤销步数:0
[恢复] 已恢复到历史状态,当前内容:备忘录模式详解:
[状态] 当前文本内容:备忘录模式详解:
==================== 执行重做(回到状态2) ====================
[管理] 执行重做,剩余可重做步数:0
[恢复] 已恢复到历史状态,当前内容:备忘录模式详解:一种用于状态恢复的设计模式
[状态] 当前文本内容:备忘录模式详解:一种用于状态恢复的设计模式
==================== 继续编辑 ====================
[编辑] 已追加文本,当前内容:备忘录模式详解:一种用于状态恢复的设计模式,广泛应用于撤销/重做功能
[管理] 已保存状态,当前可撤销步数:2
[状态] 当前文本内容:备忘录模式详解:一种用于状态恢复的设计模式,广泛应用于撤销/重做功能
==================== 执行重做(已无可用状态) ====================
[异常] 重做失败:没有可重做的状态!
==================== 清空所有状态 ====================
[管理] 已清空所有历史状态
[状态] 当前文本内容:备忘录模式详解:一种用于状态恢复的设计模式,广泛应用于撤销/重做功能
结果分析:
- 撤销 / 重做功能正常:两次撤销能回到对应的历史状态,重做能恢复被撤销的状态;
- 边界处理有效:无重做状态时抛出异常,程序不崩溃;空文本编辑会给出警告;
- 状态安全:管理者从未修改备忘录内容,所有状态变更都由发起人控制,符合封装原则。
四、工程化优化:从 “能用” 到 “好用”

上面的基础实现已经能满足简单场景,但在实际项目中,还会遇到 “状态过大”“深拷贝浅拷贝”“线程安全” 等问题。这部分我们针对这些痛点,给出优化方案和修改后的代码。
4.1 问题 1:状态过大导致内存占用过高
如果发起人的状态很复杂(比如一个包含 100 个字段的配置类),每次创建备忘录都会复制完整状态,多次保存后会占用大量内存。
优化方案:只存储 “增量状态” 而非 “完整状态”。比如文本编辑器中,每次编辑只记录 “修改的部分 + 修改位置”,恢复时通过原始状态 + 增量计算得到目标状态。
修改后的备忘录类(增量存储):
#include <string>
#include <utility> // for pair
class TextEditor;
// 增量备忘录:只存储修改的增量(位置+内容)
class IncrementalTextMemento {
private:
// 存储增量:first=修改起始位置,second=修改的内容
std::pair<size_t, std::string> delta_;
// 私有构造和访问接口
IncrementalTextMemento(size_t pos, const std::string& content) : delta_(pos, content) {}
std::pair<size_t, std::string> getDelta() const {
return delta_;
}
friend class TextEditor;
};
// 对应的发起人修改(支持增量恢复)
class TextEditor {
private:
std::string current_content_;
// 原始状态(用于增量计算)
std::string original_content_;
public:
TextEditor() : current_content_(""), original_content_("") {}
// 追加文本(记录增量)
void appendText(const std::string& text) {
if (text.empty()) return;
size_t pos = current_content_.size(); // 记录修改位置
current_content_ += text;
// 第一次修改时保存原始状态
if (original_content_.empty()) {
original_content_ = current_content_;
}
std::cout << "[编辑] 已追加文本,当前内容:" << current_content_ << std::endl;
}
// 创建增量备忘录
IncrementalTextMemento createIncrementalMemento() const {
size_t pos = original_content_.size();
std::string delta_content = current_content_.substr(pos); // 增量内容=当前内容-原始内容
return IncrementalTextMemento(pos, delta_content);
}
// 从增量备忘录恢复
void restoreFromIncrementalMemento(const IncrementalTextMemento& memento) {
auto delta = memento.getDelta();
size_t pos = delta.first;
std::string delta_content = delta.second;
// 恢复逻辑:原始状态 + 增量内容(如果pos等于原始状态长度,就是追加;否则是替换)
current_content_ = original_content_.substr(0, pos) + delta_content;
std::cout << "[恢复] 已恢复增量状态,当前内容:" << current_content_ << std::endl;
}
};
优化效果:内存占用从 “完整状态大小 × 保存次数” 降低到 “增量大小 × 保存次数”,尤其适合大状态对象的频繁保存。
4.2 问题 2:深拷贝 vs 浅拷贝导致状态污染
如果发起人的状态包含指针(比如char*、自定义对象指针),默认的浅拷贝会导致备忘录和发起人共享同一块内存,一方修改会影响另一方(状态污染)。
优化方案:对包含指针的状态执行深拷贝,确保备忘录的状态是独立的。
修改后的备忘录类(深拷贝支持):
#include <cstring>
class ConfigEditor;
// 状态包含指针的备忘录(需要深拷贝)
class ConfigMemento {
private:
// 模拟复杂状态:动态分配的字符串(指针类型)
char* config_data_;
size_t data_len_;
// 深拷贝构造(关键)
void deepCopy(const char* data, size_t len) {
data_len_ = len;
config_data_ = new char[len + 1];
strncpy(config_data_, data, len);
config_data_[len] = '\0';
}
// 私有构造
ConfigMemento(const char* data, size_t len) {
deepCopy(data, len);
}
// 私有访问接口
char* getConfigData() const {
return config_data_;
}
size_t getDataLen() const {
return data_len_;
}
// 析构函数:释放动态内存
~ConfigMemento() {
delete[] config_data_;
}
friend class ConfigEditor;
};
// 对应的发起人(包含指针状态)
class ConfigEditor {
private:
char* config_;
size_t len_;
public:
ConfigEditor() : config_(nullptr), len_(0) {}
// 设置配置(动态分配内存)
void setConfig(const char* data) {
if (config_) delete[] config_;
len_ = strlen(data);
config_ = new char[len_ + 1];
strcpy(config_ , data);
std::cout << "[配置] 已设置配置:" << config_ << std::endl;
}
// 创建备忘录(深拷贝)
ConfigMemento createMemento() const {
return ConfigMemento(config_, len_);
}
// 恢复备忘录(深拷贝)
void restoreFromMemento(const ConfigMemento& memento) {
if (config_) delete[] config_;
len_ = memento.getDataLen();
config_ = new char[len_ + 1];
strncpy(config_, memento.getConfigData(), len_);
config_[len_] = '\0';
std::cout << "[恢复] 已恢复配置:" << config_ << std::endl;
}
~ConfigEditor() {
delete[] config_;
}
};
关键说明:备忘录和发起人都对动态内存执行深拷贝,各自持有独立的内存空间,避免了状态污染。实际项目中,建议用std::string、std::vector等 RAII 容器替代裸指针,减少内存管理错误。
4.3 问题 3:线程安全问题
如果多个线程同时操作发起人(修改状态)和管理者(保存 / 恢复备忘录),可能会导致 “状态不一致”(比如线程 A 正在修改状态,线程 B 同时保存备忘录,得到的是不完整的状态)。
优化方案:在发起人、管理者的关键方法中加入互斥锁(std::mutex),保证同一时间只有一个线程操作状态。
修改后的管理者类(线程安全):
#include <mutex>
class MementoCaretakerThreadSafe {
private:
std::vector<TextMemento> undo_stack_;
std::vector<TextMemento> redo_stack_;
std::mutex mutex_; // 互斥锁
public:
void saveMemento(const TextMemento& memento) {
std::lock_guard<std::mutex> lock(mutex_); // RAII锁,自动释放
undo_stack_.push_back(memento);
redo_stack_.clear();
}
TextMemento undo() {
std::lock_guard<std::mutex> lock(mutex_);
if (undo_stack_.empty()) {
throw std::runtime_error("无撤销状态");
}
TextMemento latest = undo_stack_.back();
undo_stack_.pop_back();
redo_stack_.push_back(latest);
return latest;
}
// 其他方法类似,都加入锁...
};
关键说明:用std::lock_guard(RAII 机制)避免死锁,确保每个操作都是原子的。发起人如果有多个线程修改状态,也需要在createMemento、restoreFromMemento等方法中加锁。
五、真实场景落地:备忘录模式的 3 个典型应用
基础实现和优化讲完,我们来看备忘录模式在实际项目中的落地场景,让你知道 “什么时候该用”。
5.1 场景 1:游戏存档功能

游戏中的 “存档 / 读档” 是备忘录模式的经典应用:
- 发起人(Originator):游戏角色(
Player类),包含等级、血量、装备、位置等状态; - 备忘录(Memento):存档文件(
PlayerMemento类),存储角色的核心状态; - 管理者(Caretaker):存档管理器(
SaveManager类),负责存储多个存档(存档 1、存档 2、快速存档)。
核心代码片段:
class PlayerMemento {
private:
int level_;
int hp_;
std::string equipment_;
PlayerMemento(int level, int hp, const std::string& eq) : level_(level), hp_(hp), equipment_(eq) {}
// getter...
friend class Player;
};
class Player {
private:
int level_;
int hp_;
std::string equipment_;
public:
void fightBoss() { // 打怪(状态变化)
hp_ -= 50;
std::cout << "打BOSS后,血量:" << hp_ << std::endl;
}
PlayerMemento saveGame() { // 创建备忘录(存档)
return PlayerMemento(level_, hp_, equipment_);
}
void loadGame(const PlayerMemento& m) { // 恢复备忘录(读档)
level_ = m.level_;
hp_ = m.hp_;
equipment_ = m.equipment_;
std::cout << "读档成功,当前等级:" << level_ << ",血量:" << hp_ << std::endl;
}
};
// 管理者:存档管理器
class SaveManager {
private:
std::map<std::string, PlayerMemento> saves_; // 键:存档名称("存档1"),值:备忘录
public:
void save(const std::string& saveName, const PlayerMemento& m) {
saves_[saveName] = m;
}
PlayerMemento load(const std::string& saveName) {
return saves_.at(saveName);
}
};
5.2 场景 2:配置中心的版本管理

配置中心(比如 Nacos、Apollo)需要支持 “配置回滚”:当新配置上线出现问题时,能快速回滚到之前的稳定版本。
- 发起人(Originator):配置对象(
Config类),包含服务地址、超时时间、阈值等配置项; - 备忘录(Memento):配置版本(
ConfigVersion类),存储某一版本的完整配置; - 管理者(Caretaker):版本管理器(
VersionManager类),存储配置的所有历史版本,支持按版本号回滚。
5.3 场景 3:数据库事务回滚

数据库的事务(ACID 特性)中,“回滚(Rollback)” 功能本质上也是备忘录模式的应用:
- 发起人(Originator):数据库表(
Table类),包含数据记录; - 备忘录(Memento):事务日志(
TransactionLog类),存储事务执行前的数据状态; - 管理者(Caretaker):事务管理器(
TransactionManager类),管理事务日志,事务失败时调用发起人恢复状态。
六、避坑指南:备忘录模式的常见问题与解决方案

6.1 坑 1:备忘录过多导致内存溢出
问题:如果频繁保存备忘录(比如文本编辑器每输入一个字符就保存),会产生大量备忘录对象,占用过多内存。解决方案:
- 限制备忘录数量(比如最多保存 100 步撤销记录,超出则删除最旧的);
- 采用增量存储(如 4.1 节所述),只存储变化的部分;
- 定期序列化备忘录到磁盘(比如超过 50 步就写入文件),内存中只保留最近的记录。
6.2 坑 2:发起人状态扩展困难
问题:如果发起人新增了状态字段(比如文本编辑器新增 “字体大小” 状态),需要修改备忘录类的构造函数、状态存储和访问接口,违反 “开闭原则”。解决方案:
- 用反射(C++ 中可通过 RTTI 或第三方库如 Boost.Reflection)动态获取和设置发起人状态,备忘录只存储键值对(
std::map<std::string, std::any>); - 把状态封装成独立的 “状态类”(比如
EditorState),发起人持有该类对象,备忘录只存储EditorState的拷贝,新增状态只需修改EditorState类。
6.3 坑 3:备忘录的序列化与持久化
问题:内存中的备忘录在程序重启后会丢失,无法支持 “跨会话恢复”(比如游戏关闭后重新打开,之前的存档还在)。解决方案:
- 实现备忘录的序列化(比如用 JSON、Protobuf),将状态写入文件或数据库;
- 备忘录类提供
serialize()(序列化)和deserialize()(反序列化)方法,管理者负责调用这两个方法完成持久化和加载。
七、模式对比:备忘录模式 vs 原型模式
很多开发者会混淆备忘录模式和原型模式,因为两者都涉及 “对象拷贝”。我们用表格清晰对比:
| 对比维度 | 备忘录模式 | 原型模式 |
|---|---|---|
| 核心目的 | 保存对象状态,支持后续恢复 | 快速创建对象副本(克隆) |
| 状态访问 | 备忘录仅对发起人开放状态,封装性强 | 原型的状态对客户端可见(需要实现克隆接口) |
| 适用场景 | 撤销 / 重做、存档 / 读档、事务回滚 | 对象创建成本高(如初始化需要查询数据库)、需要快速克隆多个相似对象 |
| 实现重点 | 状态的安全存储与恢复 | 对象的深拷贝 / 浅拷贝实现 |
简单说:需要 “恢复状态” 用备忘录模式,需要 “快速克隆” 用原型模式。
八、总结
备忘录模式的核心价值在于 “在不破坏封装的前提下,实现状态的安全存储与恢复”。它通过 “发起人 - 备忘录 - 管理者” 的三元结构,将状态管理逻辑与核心业务逻辑分离,让代码更清晰、更易维护。
本文从实际场景出发,用 C++ 实现了支持多步撤销 / 重做的文本编辑器,再通过工程化优化解决了内存、拷贝、线程安全等问题,最后介绍了真实项目中的落地场景和避坑指南,内容覆盖从 “入门” 到 “实战” 的全流程。
如果你在开发中遇到 “需要回滚状态” 的需求(比如编辑器、游戏、配置管理等),备忘录模式绝对是你的首选 —— 它就像一个 “时光机”,让对象的状态随时可以 “回到过去”,帮你避免因误操作导致的各种问题。

被折叠的 条评论
为什么被折叠?



