C++备忘录模式:优雅实现对象状态保存与恢复
在开发中,我们常会遇到需要撤销操作、历史记录或状态回滚的场景。备忘录模式(Memento Pattern)正是为此而生。它允许在不破坏封装性的前提下捕获对象的内部状态,以便后续恢复。今天我们就来聊聊如何在 C++ 中落地这一模式,顺便避避坑。
核心角色解析
理解备忘录模式,关键看三个角色怎么配合:
1. Originator(发起人)
这是需要保存状态的对象。它负责创建备忘录并记录当前状态,也能利用备忘录恢复之前的状态。简单来说,它是状态的拥有者。
2. Memento(备忘录)
这是存储 Originator 状态的对象。它有两个接口:宽接口供 Originator 使用以存取数据,窄接口供其他对象使用仅用于保存。这样既保证了数据能存下来,又防止了外部随意修改内部细节。
3. Caretaker(管理者)
它只负责管理备忘录对象,比如存一堆快照。CareTaker 不操作备忘录内容,只能通过窄接口交互。职责单一,耦合度低。
设计原则体现
这个模式其实暗合了不少设计原则:
- 单一职责:把状态保存从业务逻辑里剥离出来,Originator 专心干活。
- 开闭原则:想扩展保存功能?加个新备忘录类就行,不用改 Originator。
- 迪米特法则:Caretaker 不懂 Memento 内部细节,系统更松耦合。
C++ 实战代码
下面是一个完整的示例。注意,这里我用了智能指针来管理内存,避免手动 delete 带来的泄漏风险,这在 C++ 里是必须的。
#include <iostream>
#include <string>
#include <vector>
#include <memory>
// 备忘录类:存储状态
// 只有 Originator 可以通过友元访问私有成员
class Memento {
private:
std::string state;
friend class Originator;
Memento(const std::string& s) : state(s) {}
public:
// 窄接口:获取状态(通常由 Originator 调用)
std::string GetState() const { return state; }
};
// 发起人类:产生和恢复状态
class Originator {
private:
std::string state;
public:
void SetState(const std::string& s) {
std::cout << "设置状态:" << s << std::endl;
state = s;
}
// 创建备忘录
std::unique_ptr<Memento> CreateMemento() {
return std::make_unique<Memento>(state);
}
// 恢复状态
void RestoreFromMemento(const Memento* m) {
state = m->GetState();
std::cout << "恢复状态:" << state << std::endl;
}
};
// 管理者类:保管备忘录
// 使用 unique_ptr 自动管理生命周期
class Caretaker {
private:
std::vector<std::unique_ptr<Memento>> mementos;
Originator* originator;
public:
Caretaker(Originator* orig) : originator(orig) {}
void Save() {
mementos.push_back(originator->CreateMemento());
}
void Undo() {
if (mementos.empty()) return;
auto m = mementos.back();
originator->RestoreFromMemento(m.get());
mementos.pop_back();
}
};
int main() {
Originator originator;
Caretaker caretaker(&originator);
originator.SetState("状态 1");
caretaker.Save();
originator.SetState("状态 2");
caretaker.Save();
originator.SetState("状态 3");
// 执行撤销,回到状态 2
caretaker.Undo();
// 再撤销,回到状态 1
caretaker.Undo();
return 0;
}
写这段代码时,有几个点值得注意:
- 内存安全:C++ 里指针满天飞,用
std::unique_ptr能让资源管理更省心,尤其是 Caretaker 里的备忘录列表。 - 封装性:Memento 的构造函数是私有的,防止外部直接实例化,保证状态只能由 Originator 生成。
- 接口隔离:Caretaker 拿到的只是
const Memento*,没法篡改里面的state。
典型应用场景
这模式不是万能的,但在这些地方很香:
- 撤销/重做功能:文本编辑器最经典的应用。
- 事务回滚:数据库操作失败时恢复现场。
- 游戏存档:保存进度,随时读档。
- 配置管理:保留历史配置版本。
高级特性与优化
实际项目中,还得考虑这些情况:
- 增量备忘录:如果状态变化不大,别每次都存全量,只记差异能省内存。
- 序列化支持:想把快照存到文件或数据库?加上序列化逻辑就行。
- 线程安全:多线程环境下,记得加锁保护共享状态。
与其他模式的协作
备忘录很少单打独斗,常和命令模式一起出现。命令模式负责封装动作,备忘录负责记录动作前的状态,两者结合就能做出完美的撤销栈。
注意事项
最后提醒几个坑:
- 内存消耗:大对象频繁快照会吃内存,按需保存。
- 性能开销:深拷贝状态可能耗时,评估一下场景。
- 替代方案:有时候原型模式(克隆)比备忘录更简单,看需求选。
总结
备忘录模式通过状态封装实现了灵活的回滚机制,是面向对象设计的优秀实践。在 C++ 中实现时,重点要处理好内存管理和接口边界。掌握它,能让你的系统更加健壮、易维护。

