1. 备忘录模式:C++中的时光机
在游戏开发中,你有没有遇到过这样的场景:玩家误操作导致角色死亡,需要回退到几秒前的状态;或者在策略游戏中,用户想撤销上一步操作重新布局。这种"后悔药"机制,本质上就是备忘录模式(Memento Pattern)的典型应用。作为行为型设计模式三巨头之一(另外两个是观察者和策略模式),它能在不破坏封装性的前提下,捕获并外部化对象的内部状态。
我最早在开发回合制战棋游戏时深刻体会到它的价值。当玩家移动角色后发现陷入包围圈,一句简单的game.undo()就能让棋盘状态回退到操作前,这种丝滑的体验背后正是备忘录模式在支撑。与命令模式的撤销不同,备忘录关注的是对象状态的快照保存与恢复,而非操作过程的逆向执行。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 模式结构与核心思想
2.1 三足鼎立的角色体系
备忘录模式的经典实现包含三个关键角色:
cpp复制// 发起人(Originator)
class GameCharacter {
private:
int health;
Position pos;
// 其他状态字段...
public:
// 创建备忘录保存当前状态
Memento save() { return Memento(health, pos); }
// 从备忘录恢复状态
void restore(const Memento& m) {
health = m.getHealth();
pos = m.getPosition();
}
};
// 备忘录(Memento)
class Memento {
private:
friend class GameCharacter; // 关键:仅允许Originator访问
int health;
Position pos;
Memento(int h, Position p) : health(h), pos(p) {}
// 获取方法设为private,限制外部直接访问
int getHealth() const { return health; }
Position getPosition() const { return pos; }
};
// 管理者(Caretaker)
class SaveManager {
std::stack<Memento> history;
public:
void push(const Memento& m) { history.push(m); }
Memento pop() {
Memento top = history.top();
history.pop();
return top;
}
};
这种设计的精妙之处在于:
- 封装性保护:Memento将状态存储设为私有,仅通过
friend授权Originator访问,避免外部直接修改状态 - 职责分离:Caretaker只负责备忘录的存储管理,不关心具体内容
- 状态隔离:恢复操作时,Originator自己处理状态赋值,保证业务逻辑内聚
2.2 状态存储的三种实现策略
根据不同的使用场景,备忘录的实现方式可以灵活选择:
| 策略类型 | 实现方式 | 适用场景 | 优缺点 |
|---|---|---|---|
| 完全备份 | 复制所有字段 | 状态较简单时 | 实现简单但内存占用高 |
| 增量备份 | 只记录变化字段 | 频繁保存大对象 | 需额外处理字段变更检测 |
| 序列化备份 | 转为二进制/JSON | 需要持久化时 | 方便存储但性能较低 |
在C++中,增量备份的典型实现可以参考:
cpp复制class DiffMemento {
std::map<std::string, std::any> changes; // 字段变更记录
public:
template<typename T>
void setChange(const std::string& field, const T& value) {
changes[field] = value;
}
const auto& getChanges() const { return changes; }
};
3. 实战:游戏存档系统设计
3.1 多层级撤销栈实现
在RPG游戏中,我们通常需要支持多级撤销。以下是基于std::stack的增强实现:
cpp复制class GameStateManager {
std::stack<Memento> undoStack;
std::stack<Memento> redoStack;
size_t maxStackSize = 50; // 防止内存溢出
void saveState(const Memento& m) {
if (undoStack.size() >= maxStackSize) {
undoStack = std::stack<Memento>(); // 清空重来
}
undoStack.push(m);
redoStack = std::stack<Memento>(); // 新操作清空redo
}
Memento undo() {
if (undoStack.size() <= 1) return undoStack.top();
redoStack.push(undoStack.top());
undoStack.pop();
return undoStack.top();
}
Memento redo() {
if (redoStack.empty()) return undoStack.top();
auto state = redoStack.top();
undoStack.push(state);
redoStack.pop();
return state;
}
};
3.2 性能优化技巧
- 懒加载备忘录:对于大型游戏对象,可以采用指针+COW(Copy-On-Write)
cpp复制class LazyMemento { std::shared_ptr<GameState> state; public: explicit LazyMemento(std::shared_ptr<GameState> s) : state(std::make_shared<GameState>(*s)) {} }; - 状态压缩:对连续相同状态采用RLE压缩
cpp复制void compressStack(std::stack<Memento>& stack) { if (stack.size() < 2) return; auto top1 = stack.top(); stack.pop(); auto top2 = stack.top(); if (top1 == top2) { stack.pop(); stack.push(top1); // 去重 } else { stack.push(top1); } } - 异步持久化:将存档操作放入后台线程
cpp复制std::future<void> asyncSave(const std::string& filename) { return std::async(std::launch::async, [=] { auto memento = currentState(); std::ofstream file(filename); file << serialize(memento); // 假设有序列化方法 }); }
4. 陷阱与最佳实践
4.1 深拷贝与浅拷贝之争
当对象包含指针成员时,必须特别注意:
cpp复制class Texture {
unsigned char* pixels; // 指向堆内存
public:
Memento save() {
Memento m;
// 错误做法:m.pixels = pixels; (浅拷贝)
m.pixels = new unsigned char[size]; // 深拷贝
std::memcpy(m.pixels, pixels, size);
return m;
}
};
建议采用std::unique_ptr等智能指针管理资源:
cpp复制class SafeTexture {
std::unique_ptr<unsigned char[]> pixels;
public:
Memento save() {
auto copy = std::make_unique<unsigned char[]>(size);
std::copy_n(pixels.get(), size, copy.get());
return Memento(std::move(copy));
}
};
4.2 版本兼容性处理
当游戏更新导致数据结构变化时,需要版本控制:
cpp复制struct MementoHeader {
uint32_t version;
time_t saveTime;
// 其他元数据...
};
class VersionedMemento {
public:
void load(const std::string& file) {
std::ifstream f(file);
MementoHeader header;
f.read(reinterpret_cast<char*>(&header), sizeof(header));
switch(header.version) {
case 1: loadV1(f); break;
case 2: loadV2(f); break;
// 处理旧版本兼容...
}
}
};
5. 扩展应用场景
5.1 事务性操作支持
在数据库操作中实现原子性:
cpp复制class DatabaseTransaction {
Database& db;
std::vector<Memento> backups;
public:
void begin() {
backups.clear();
backups.push_back(db.saveState());
}
void commit() {
backups.clear();
}
void rollback() {
if (!backups.empty()) {
db.restore(backups.front());
}
}
};
5.2 AI训练检查点
机器学习训练过程中的断点续训:
cpp复制class TrainingSession {
NeuralNetwork model;
public:
void saveCheckpoint(const std::string& path) {
std::ofstream file(path);
file << model.saveState().serialize();
}
void loadCheckpoint(const std::string& path) {
std::ifstream file(path);
Memento m = Memento::deserialize(file);
model.restoreState(m);
}
};
在VS Code的C++开发环境中,可以通过自定义任务实现自动备忘录:
json复制{
"label": "Build with Snapshot",
"type": "shell",
"command": "make && ./backup_build.sh ${file}",
"problemMatcher": ["$gcc"]
}
备忘录模式与原型模式结合还能实现更复杂的状态克隆。当需要保存的对象层级很深时,可以考虑使用std::enable_shared_from_this来安全地传递this指针。在多线程环境下,应当使用std::atomic或互斥锁保护备忘录栈的操作。
