1.动机
- 在应用软件的开发过程中,有时我们有必要记录一个对象的内部状态。为了允许用户取消不确定的操作或从错误中恢复过来,需要实现备份点和撤销机制,而要实现这些机制,我们必须事先将状态信息保存在某处,这样状态才能将对象恢复到它们原先的状态。
- 但是对象通常封装了其部分或所有的状态信息,使得其状态不能被其它对象访问,也就不可能在该对象之外保存其状态,而暴露其内部状态又将违反封装的原则,可能有损系统的可靠性和可扩展性。
2.定义
- 备忘录模式(Memento Pattern):在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
- 比如我们打游戏时分,如果在打大BOSS之前存档,此时就需要将对应的游戏场景,任务信息,人物信息等等状态存储起来;当赢得大BOSS之后,覆盖之前的存档时,就将之前的存档丢弃,新建立一个存档,保存当前的状态信息;如果打输了,恢复存档,就将之前的存档信息读取出来,还原到打大BOSS之前的游戏场景,重新开始打大BOSS。这里面就是使用的备忘录模式。
-
一个备忘录是一个对象,它存储另一个对象在某个瞬间的内部状态,而后者称为备忘录的原发器。当需要设置原发器的检查点时,取消操作机制会向原发器请求一个备忘录。原发器用描述当前状态的信息初始化该备忘录。只有原发器可以向备忘录中存取信息,备忘录中的信息对其他的对象是“不可见”的。
3.结构
-
Memento:备忘录存储原发器对象的内部状态。原发器根据需要决定备忘录存储原发器的哪些内部状态;防止原发器以外的其他对象访问备忘录。备忘录实际上有两个接口,管理者只能看到备忘录的窄接口————它只能将备忘录传递给其他对象。相反,原发器能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。理想的情况是只允许生成备忘录的那个原发器访问本备忘录的内部状态;
-
Originator:原发器创建一个备忘录,用以记录当前时刻它的内部状态;我们使用备忘录恢复内部状态;
-
Caretaker:负责保存好备忘录;但是,不能对备忘录的内容进行操作或检查。
4.代码分析
#include <iostream>
using namespace std;
//备忘录
class RoleStateMemento
{
public:
RoleStateMemento(unsigned iBlood, unsigned iAttack, unsigned iDefense) :
m_iBlood(iBlood), m_iAttack(iAttack), m_iDefense(iDefense){}
private:
friend class GameRole;//声明友元类,允许GameRole类对象访问返回到先前状态所需的所有数据
unsigned GetBloodValue() { return m_iBlood; }
unsigned GetAttackValue() { return m_iAttack; }
unsigned GetDefenseValue() { return m_iDefense; }
unsigned m_iBlood; // 生命力
unsigned m_iAttack; // 攻击力
unsigned m_iDefense; // 防御力
};
//原发器:以记录当前时刻的内部状态
class GameRole
{
public:
GameRole() : m_iBlood(100), m_iAttack(100), m_iDefense(100){}
// 产生需要存档的当前的数据,将这个对象给管理者就可以完成存档
RoleStateMemento *SaveState() { return new RoleStateMemento(m_iBlood, m_iAttack, m_iDefense); }
// 恢复之前的存档,从备忘录里获取所需的数据
void RecoveryState(RoleStateMemento *pRoleState)
{
m_iBlood = pRoleState->GetBloodValue();
m_iAttack = pRoleState->GetAttackValue();
m_iDefense = pRoleState->GetDefenseValue();
cout << "Recovery..." << endl;
}
void ShowState()
{
cout << "Blood:" << m_iBlood << endl;
cout << "Attack:" << m_iAttack << endl;
cout << "Defense:" << m_iDefense << endl;
cout << endl;
}
void Fight()
{
m_iBlood -= 100;
m_iAttack -= 10;
m_iDefense -= 20;
if (m_iBlood == 0)
{
cout << "Game Over" << endl;
cout << endl;
}
}
void LevelUp()
{
m_iBlood += 100;
m_iAttack += 10;
m_iDefense += 20;
cout << "Level +1!" << endl;
}
private:
unsigned m_iBlood; // 生命力
unsigned m_iAttack; // 攻击力
unsigned m_iDefense; // 防御力
};
//管理者:负责保存备忘录
class RoleStateCaretaker
{
public:
void SetRoleStateMemento(RoleStateMemento *pRoleStateMemento) { m_pRoleStateMemento = pRoleStateMemento; }
RoleStateMemento *GetRoleStateMemento() { return m_pRoleStateMemento; }
private:
RoleStateMemento *m_pRoleStateMemento;
};
int main()
{
GameRole *pLiXY = new GameRole(); // 创建李逍遥这个角色
pLiXY->ShowState(); // 显示初始的状态
// 初始化一个管理者,用来保存备忘录信息
RoleStateCaretaker *pRoleStateCaretaker = new RoleStateCaretaker();
// 升级,
pLiXY->LevelUp();
pRoleStateCaretaker->SetRoleStateMemento(pLiXY->SaveState());
pLiXY->ShowState();
// 开始打BOSS
pLiXY->Fight();
pLiXY->ShowState();
// 打BOSS失败,读档,从新开始
pLiXY->RecoveryState(pRoleStateCaretaker->GetRoleStateMemento());
pLiXY->ShowState();
system("pause");
return 0;
}
5.优点
-
给用户提供了一种可以恢复状态的机制。可以是用户能够比较方便地回到某个历史的状态。
-
实现了信息的封装。使得用户不需要关心状态的保存细节。
6.缺点
- 备忘录模式的最大缺点就是资源消耗过大,如果类的成员变量太多,就不可避免占用大量的内存了,而且每保存一次对象的状态都需要消耗内存资源。
7.适用场景
- 必须保存一个对象在某一个时刻的部分或完整状态,这样以后需要时它才能恢复到先前的状态;
- 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性。
8.总结
- 备忘录模式在实际应用中也不少;我们在进行文档编辑时,经常使用的撤销操作。使用C++实现备忘录模式的关键点在于Originator类是Memento的友元类,这样就使得管理备忘录的Caretaker对象,以及其它对象都不能读取、设置备忘录,只有Originator类才能进行备忘录的读取和设置。由于备忘录主要是用于对对象的状态进行备份,实现了撤销操作,如果对象的状态数据很大很多时,在进行备忘时,就会很占用资源,这个是我们在实际开发时需要考虑的东西。结合之前的设计模式,在总结命令模式时,说到命令模式支持事物的回退,而这个就是依靠的备忘录模式来实现的。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。