备忘录模式(行为型)

前言


在玩RPG之类游戏的时候,每次通过一次游戏关卡的时候,一般我们都会把游戏的角色状态保存一下,继续通下一关,如果下一关难度比较大很难通过失败后,又懒得从第一关开始重新。幸亏有关卡的保存功能,我们可以直接从上次保存的关卡中重新开始即可,提高了通关的效率。这里的游戏角色的保存和恢复功能就是本文将要介绍的备忘录模式。

备忘录模式


备忘录模式提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原,很多软件用到的撤销功能就是基于此模式的

一个备忘录是一个对象,它存储另一个对象在某个瞬间的内部状态,而后者称为备忘录的原发器(Originator)。当需要设置原发器的检查点时, 取消操作机制会向原发器请求一个备忘录。原发器用描述当前状态的信息初始化该备忘录。只有原发器可以向备忘录中存取信息,备忘录对其他的对象“不可见”

意图

在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以将该对象恢复到原先保存的状态

参与者

  • Memento
    备忘录,存储原发器对象的内部状态。原发器根据需要决定备忘录存储原发器的哪些内部状态
    防止原发器以外的其他对象访问备忘录。备忘录实际上有两个接口:
    1.管理者(Caretaker)只能看到备忘录的窄接口—它只能将备忘录传递给其他对象;
    2.相反,原发器能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。理想的情况是只允许生成本备忘录的那个原发器访问本备忘录的内部状态

  • Originator
    原发器,创建一个备忘录,用以记录当前时刻它的内部状态
    使用备忘录恢复内部状态

  • Caretaker
    负责人,负责保存好备忘录
    不能对备忘录的内容进行操作或检查

管理器向原发器请求一个备忘录, 保留一段时间后,将其送回给原发器
备忘录是被动的。只有创建备忘录的原发器会对它的状态进行赋值和检索

模式结构

memento

代码实现

1.首先定义备忘录类Memento,备忘录类内部状态成员是私有的为了保证封装性,只有原发器能看到,声明原发器类为friend

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//状态信息类
struct MementoState
{
private:
string m_stateinformation;
public:
MementoState(string state): m_stateinformation(state) {};
public:
string GetStateInform()
{
return m_stateinformation;
}
};

// 备忘录
class Originator;
class Memento
{
private:
MementoState m_state;
public:
Memento(MementoState state) : m_state(state) {};
friend class Originator; // Originator为友元类,只有Originator才能访问Memento成员
};

2.再定义原发器类Originator,并提供创建备忘录的接口SaveStateToCreatedMemento()和恢复备忘录的接口LoadStateFromMemento():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 原发器
class Originator
{
private:
MementoState m_state;
public:
Originator(MementoState state) : m_state(state) {};
public:
Memento* SaveStateToCreatedMemento()
{
Memento *pMemento = new Memento(m_state); // 生成备忘录
return pMemento;
}
void LoadStateFromMemento(Memento *pMemento) // 恢复备忘录
{
m_state = pMemento->m_state; // 只有Originator才能访问Memento的私有成员
}
void ChangeState(MementoState state)
{
m_state = state;
}
void ShowState()
{
cout << "State Information Is: " << m_state.GetStateInform() << endl;
}
};

3.再定义备忘录的管理者类Caretaker,该类负责管理Mememto,负责保存备忘录,但是不能对备忘录的内容进行操作或检查,不能修改对象,也无须知道对象的实现细节:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 备忘录管理
class Caretaker
{
private:
vector<Memento*> m_vecMemento; // vector 支持随机存取,list不支持,保存多个备忘录对象,可以支持多次撤销和恢复
public:
Caretaker() {};
virtual ~Caretaker()
{
for (vector<Memento*>::iterator it = m_vecMemento.begin();
it != m_vecMemento.end();
++it)
{
SAFE_RELASE_POINTER(*it); // 备忘录管理者负责释放备忘录对象内存
}
m_vecMemento.clear();
}
public:
void SaveMemento(Memento *pMemento)
{
m_vecMemento.push_back(pMemento);
}
Memento* LoadMementoByIndex(int MementoIndex)
{
return m_vecMemento[MementoIndex];
}
};

4.测试备忘录模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
void MementoTest_General()
{
Caretaker *pCT = new Caretaker();
MementoState state1("First Step");
MementoState state2("Second Step");
MementoState state3("Third Step");

Originator *pO = new Originator(state1);
pO->ShowState();

pCT->SaveMemento(pO->SaveStateToCreatedMemento()); // Caretaker 管理添加新建的第一个Memento
pO->ChangeState(state2);
pO->ShowState();

pCT->SaveMemento(pO->SaveStateToCreatedMemento()); // Caretaker 管理添加新建的第二个Memento
pO->ChangeState(state3);
pO->ShowState();

cout << "Restore State 1: ";
pO->LoadStateFromMemento(pCT->LoadMementoByIndex(1)); // 从Caretaker中取第二个Memento保存的状态
pO->ShowState();

cout << "Restore State 0: ";
pO->LoadStateFromMemento(pCT->LoadMementoByIndex(0)); // 从Caretaker中取第一个Memento保存的状态
pO->ShowState();

SAFE_RELASE_POINTER(pO);
SAFE_RELASE_POINTER(pCT);
}

5.运行结果:

State Information Is: First Step
State Information Is: Second Step
State Information Is: Third Step
Restore State 1: State Information Is: Second Step
Restore State 0: State Information Is: First Step

备忘录的封装:
备忘录是一个很特殊的对象,只有原发器对它拥有控制的权力,负责人只负责管理,而其他类无法访问到备忘录,它只需要从负责人处取出备忘录对象并将原发器对象的状态恢复,而无须关心备忘录的保存细节。理想的情况是只允许生成该备忘录的那个原发器访问备忘录的内部状态,
在C++中可以使用friend关键字,让原发器类和备忘录类成为友元类,互相之间可以访问对象的一些私有的属性;在Java语言中可以将原发器类和备忘录类放在一个包中,让它们之间满足默认的包内可见性;也可以将备忘录类作为原发器类的内部类,使得只有原发器才可以访问备忘录中的数据

使用场景

  • 必须保存一个对象在某一个时刻的(部分)状态, 这样以后需要时它才能恢复到先前的状态
  • 如果一个用接口来让其它对象直接得到这些状态,将会暴露对象的实现细节并破坏对象的封装性

优缺点

  • 优点
    • 提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤
    • 备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作
  • 缺点
    • 如果原发器在生成备忘录时必须拷贝并存储大量的信息或者客户非常频繁地创建备忘录和恢复原发器状态,可能会导致非常大的开销

存储增量式改变:如果备忘录的创建及其返回(给它们的原发器)的顺序是可预测的,备忘录可以仅存储原发器内部状态的增量改变

备忘录模式具体实例


游戏角色状态保存恢复问题

使用备忘录模式简单实现前言所述的游戏角色保存和恢复功能

代码实现
1.定义游戏角色备忘录类GameRoleMemento:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 游戏角色备忘录
class GameRole;
class GameRoleMemento
{
private:
int m_RoleAgileValue;
int m_RolePowerValue;
int m_RoleIntelligenceValue;
public:
GameRoleMemento(int agile, int powr, int intelligence)
: m_RoleAgileValue(agile), m_RolePowerValue(powr), m_RoleIntelligenceValue(intelligence) {};
friend class GameRole;
};

2.定义游戏角色原发器类GameRole,包含创建和恢复游戏角色备忘录的接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 游戏角色(原发器)
class GameRole
{
private:
int m_RoleAgileValue;
int m_RolePowerValue;
int m_RoleIntelligenceValue;
public:
GameRole(int agile, int powr, int intelligence)
: m_RoleAgileValue(agile), m_RolePowerValue(powr), m_RoleIntelligenceValue(intelligence) {};
public:
GameRoleMemento* CreateMemento()
{
return new GameRoleMemento(m_RoleAgileValue, m_RolePowerValue, m_RoleIntelligenceValue);
}
void SetMemento(GameRoleMemento *pGameRoleMemento)
{
m_RoleAgileValue = pGameRoleMemento->m_RoleAgileValue;
m_RolePowerValue = pGameRoleMemento->m_RolePowerValue;
m_RoleIntelligenceValue = pGameRoleMemento->m_RoleIntelligenceValue;
}
void Attack()
{
m_RolePowerValue-= 10;
}
void ShowAttribute()
{
cout << "Hero Attribution: [Agile]:" << m_RoleAgileValue << " [Power]:" << m_RolePowerValue <<
" [Intelligence]:" << m_RoleIntelligenceValue << endl;
}
};

3.定义游戏角色备忘录管理者类GameRoleCaretaker,负责管理游戏角色备忘录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 游戏角色备忘录管理者
class GameRoleCaretaker
{
private:
vector<GameRoleMemento*> m_vecGameRoleMemento;
public:
GameRoleCaretaker() {};
virtual ~GameRoleCaretaker()
{
for (vector<GameRoleMemento*>::iterator it = m_vecGameRoleMemento.begin();
it != m_vecGameRoleMemento.end();
++it)
{
SAFE_RELASE_POINTER(*it);
}
m_vecGameRoleMemento.clear();
}
public:
void SaveGameRoleMemento(GameRoleMemento *pGRM)
{
m_vecGameRoleMemento.push_back(pGRM);
}
GameRoleMemento* LoadGameRoleMementoByIndex(int GameRoleMementoIndex)
{
return m_vecGameRoleMemento[GameRoleMementoIndex];
}
};

4.测试备忘录模式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void MementoTest_GameRole()
{
GameRoleCaretaker *pGRCT = new GameRoleCaretaker();

//原始属性状态
GameRole *pGR = new GameRole(50,50,50);
pGR->ShowAttribute();

//攻击一次后的状态,先保存,并显示属性
pGRCT->SaveGameRoleMemento(pGR->CreateMemento());
pGR->Attack();
pGR->ShowAttribute();


//再攻击一次后的状态,先保存,并显示属性
pGRCT->SaveGameRoleMemento(pGR->CreateMemento());
pGR->Attack();
pGR->ShowAttribute();

//恢复第二次攻击前的状态,并显示属性
cout << "Game Role Restore 1: ";
pGR->SetMemento(pGRCT->LoadGameRoleMementoByIndex(1));
pGR->ShowAttribute();

//恢复第一次攻击前的状态,并显示属性
cout << "Game Role Restore 0: ";
pGR->SetMemento(pGRCT->LoadGameRoleMementoByIndex(0));
pGR->ShowAttribute();

SAFE_RELASE_POINTER(pGRCT);
SAFE_RELASE_POINTER(pGR);
}

5.运行结果:

Hero Attribution: [Agile]:50 [Power]:50 [Intelligence]:50
Hero Attribution: [Agile]:50 [Power]:40 [Intelligence]:50
Hero Attribution: [Agile]:50 [Power]:30 [Intelligence]:50
Game Role Restore 1: Hero Attribution: [Agile]:50 [Power]:40 [Intelligence]:50
Game Role Restore 0: Hero Attribution: [Agile]:50 [Power]:50 [Intelligence]:50