一.简介
今天来学习一下设计模式中的备忘录模式。说到这个模式,也许不太熟悉,不过相信写代码的时候,写错了之后,Ctrl+Z的功能用得还是比较多的,比如Word,Photoshop等编辑软件中,撤销功能都是必不可少的。而备忘录模式就是一个很好地实现撤销功能的一种设计模式。它可以让我们的系统恢复到之前的状态,嗯哼,传说中的月光宝盒!
下面看一下备忘录模式的定义以及UML类图:
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
二.备忘录模式的例子
说到备忘录模式,在游戏中还是灰常有用的!传说中的SL大法(soave load)可是帮我打过了无数的游戏,每逢boss必存档也是我一直信奉的信条。这次,我们就来通过一个简单的游戏的例子来看一下备忘录模式的应用。
没有用备忘录模式的情况我就不说了,被Boss秒了之后,“大侠请重新来过”的时候发现没存档,后果不是删游戏就是摔键盘...
不多说,上代码,看一下备忘录模式的例子:
// Design Pattern.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <iostream>
#include <vector>
using namespace std;
//玩家存档类,备份,相当于模式中的Memento
class PlayerMemento
{
//这里我们将其作为Player类的友元类,可以直接访问其private变量
friend class Player;
private:
int m_hp; //血量
int m_ATK; //攻击
int m_DEF; //防御
public:
PlayerMemento(int hp, int atk, int def)
: m_hp(hp), m_ATK(atk), m_DEF(def)
{
}
};
//存档点,存储备忘录的地方,相当于Caretake
class CheckPoint
{
private:
vector<PlayerMemento*> m_MementoVector;
public:
//存档
void Save(PlayerMemento* memento)
{
m_MementoVector.push_back(memento);
}
//根据存档编号获得存档
PlayerMemento* Load(int num)
{
return m_MementoVector[num];
}
//表忘了清空对象
void Clear()
{
for (vector<PlayerMemento*>::iterator it = m_MementoVector.begin(); it != m_MementoVector.end(); ++it)
{
delete *it;
}
m_MementoVector.resize(0);
}
};
//玩家类,就是我们需要备份的类,相当于备忘录模式里面的Originator.
class Player
{
private:
int m_hp; //血量
int m_ATK; //攻击
int m_DEF; //防御
public:
Player(int hp, int atk, int def)
: m_hp(hp), m_ATK(atk), m_DEF(def)
{
}
PlayerMemento* Save()
{
cout << "要去打Boss了,好紧张,存个档!" << endl;
return new PlayerMemento(m_hp, m_ATK, m_DEF);
}
void Load(PlayerMemento* memento)
{
m_hp = memento->m_hp;
m_ATK = memento->m_ATK;
m_DEF = memento->m_DEF;
cout << "哇咔咔,有存档,SL大法好!" << endl;
}
void Display()
{
cout << "当前生命: " << m_hp << " 当前攻击: " << m_ATK << " 当前防御: " << m_DEF << endl;
}
//受伤,减血
void Hurt(int v)
{
m_hp -= v;
if (m_hp <= 0)
{
m_hp = 0;
cout << "OMG!我挂了!" << endl;
}
}
};
int _tmain(int argc, _TCHAR* argv[])
{
//创建一个玩家对象,以及一个存档点对象
Player* player = new Player(100, 200, 300);
CheckPoint* checkPoint = new CheckPoint();
player->Display();
//每逢Boss必存档
checkPoint->Save(player->Save());
//受到10000点伤害,被boss秒了
player->Hurt(10000);
player->Display();
//我有存档,我任性!
player->Load(checkPoint->Load(0));
player->Display();
system("pause");
return 0;
}
结果:
当前生命: 100 当前攻击: 200 当前防御: 300
要去打Boss了,好紧张,存个档!
OMG!我挂了!
当前生命: 0 当前攻击: 200 当前防御: 300
哇咔咔,有存档,SL大法好!
当前生命: 100 当前攻击: 200 当前防御: 300
请按任意键继续. . .
我们简单看一下上面的程序,玩家有一些属性字段,游戏的进程也是跟游戏中各个对象的状态属性有关,当然肯定比这个复杂得多。要想完全还原一个游戏进程,我们就需要恢复对象到之前的状态。我们给玩家建立了一个备忘录类,里面只是封装了玩家的相关属性字段,通过玩家的Save方法,创建出这样一个对象,保存了当前玩家的所有状态,存储到CheckPoint对象中,CheckPoint对象只有保存和根据需要获得存档的功能。当我们需要恢复对象状态时,只需要从checkpoint对象中获得备忘录对象,通过玩家的Load方法根据备忘录恢复原有属性。
三.备忘录模式的总结
最后我们来看一下备忘录模式的优点缺点以及使用时机。
优点:
1)提供了一种对象状态恢复的机制,让我们可以在对象当前状态不对或者不符合预期时将对象恢复到之前备份时的状态,而且也可以是部分属性,不必所有字段都备份。
2)备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。
缺点:
如果存储的状态对象比较多或者状态属性较多时,可能会有较大的性能开销。
使用时机:
当我们有可能需要将对象状态恢复到某些时刻之前时,我们就可以考虑使用备忘录模式,比如撤销操作,游戏的存档之类的。