在上一篇文章中,我们初步了解了备忘录模式的概念和基本实现。但实际应用中,仅仅保存对象的完整状态可能效率低下,而且某些敏感信息并不适合直接暴露。本文将继续深入探讨备忘录模式,重点解决状态存储优化和信息安全问题,并通过 C++ 代码示例进行详细讲解。
问题场景重现:大型对象的状态保存
假设我们正在开发一个游戏,玩家的角色拥有大量的属性(生命值、魔法值、装备、背包物品等)。每次玩家进行重要操作(如进入新地图、升级)时,都需要保存角色的状态以便回退。如果每次都完整复制整个角色对象,会消耗大量的内存和 CPU 资源。这就像我们在使用 Nginx 反向代理时,如果每次请求都记录完整的请求头和 body,在高并发场景下会对服务器造成巨大的压力。我们可以考虑只记录关键的变化部分,类似 Nginx 的 access log 只记录关键信息。
底层原理深度剖析:增量式备忘录
增量式备忘录的核心思想是:只保存对象状态的差异,而不是完整状态。这需要我们在对象内部维护一个状态变化的历史记录,并在创建备忘录时,只保存最近的变化。在恢复状态时,按照相反的顺序应用这些变化,即可恢复到之前的状态。这种方式可以极大地减少内存消耗,尤其是在对象状态变化不频繁的情况下。
具体代码/配置解决方案:C++ 增量式备忘录实现
下面是一个简单的 C++ 示例,演示如何使用增量式备忘录模式。
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
class Originator {
public:
Originator(int health, int mana, std::string item) : health_(health), mana_(mana), item_(item) {}
void setHealth(int health) {
history_.push_back([this, health]() { // 使用 lambda 表达式记录变化
int oldHealth = health_; // 记录旧值
health_ = health; // 设置新值
return [this, oldHealth]() { // 返回一个 undo 操作的 lambda 表达式
health_ = oldHealth; // 恢复旧值
};
});
health_ = health;
}
void setMana(int mana) {
history_.push_back([this, mana]() {
int oldMana = mana_; // 记录旧值
mana_ = mana; // 设置新值
return [this, oldMana]() {
mana_ = oldMana; // 恢复旧值
};
});
mana_ = mana;
}
void setItem(std::string item) {
history_.push_back([this, item]() {
std::string oldItem = item_; // 记录旧值
item_ = item; // 设置新值
return [this, oldItem]() {
item_ = oldItem; // 恢复旧值
};
});
item_ = item;
}
int getHealth() const { return health_; }
int getMana() const { return mana_; }
std::string getItem() const { return item_; }
void undo() {
if (!history_.empty()) {
auto undoAction = history_.back()(); // 执行 undo 操作
undoAction(); // 恢复状态
history_.pop_back(); // 移除该操作
}
}
private:
int health_;
int mana_;
std::string item_;
std::vector<std::function<std::function<void()>()>> history_; // 存储状态变化的历史记录
};
int main() {
Originator player(100, 50, "Sword");
std::cout << "Health: " << player.getHealth() << ", Mana: " << player.getMana() << ", Item: " << player.getItem() << std::endl;
player.setHealth(80);
player.setMana(60);
player.setItem("Shield");
std::cout << "Health: " << player.getHealth() << ", Mana: " << player.getMana() << ", Item: " << player.getItem() << std::endl;
player.undo(); // 撤销上一次操作
std::cout << "Health: " << player.getHealth() << ", Mana: " << player.getMana() << ", Item: " << player.getItem() << std::endl;
player.undo(); // 再次撤销
std::cout << "Health: " << player.getHealth() << ", Mana: " << player.getMana() << ", Item: " << player.getItem() << std::endl;
return 0;
}
在这个例子中,Originator 类维护了一个 history_ 向量,用于存储状态变化的历史记录。每次调用 setHealth、setMana 或 setItem 方法时,都会创建一个 lambda 表达式来记录变化,并将该 lambda 表达式添加到 history_ 中。undo 方法用于撤销上一次操作,它从 history_ 中取出最后一个 lambda 表达式,执行它,然后将其从 history_ 中移除。
实战避坑经验总结
- 状态粒度控制:需要仔细考虑应该保存哪些状态。如果粒度太细,会导致备忘录数量过多;如果粒度太粗,则可能无法完全恢复到之前的状态。
- 线程安全问题:如果多个线程同时访问 Originator 对象,需要考虑线程安全问题,可以使用互斥锁来保护状态。
- 内存管理:如果状态对象较大,需要注意内存管理,避免内存泄漏。可以使用智能指针来管理状态对象的生命周期。
- 序列化与持久化:如果需要将备忘录持久化到磁盘,需要考虑状态对象的序列化问题。可以使用 Boost.Serialization 或其他序列化库。
- 状态加密:对于敏感信息,可以使用加密算法对状态进行加密,确保信息安全,例如使用 AES 加密算法。
总之,备忘录模式是一种非常有用的设计模式,可以帮助我们实现状态回滚和历史记录功能。在实际应用中,需要根据具体情况选择合适的实现方式,并注意一些常见的坑点。 希望通过本文,您能对 设计模式 - 备忘录模式 有更深入的理解。
冠军资讯
脱发程序员