四时宝库

程序员的知识宝库

面向对象的可复用设计模式之中介者模式(19/24)

备忘录模式(Memento Pattern)又叫做快照模式(Snapshot Pattern)或Token模式,是GoF的23种设计模式之一,属于行为模式。

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

当你需要让对象返回之前的状态时(例如, 你的用户请求“撤销”), 可以使用备忘录模式。

该模式用于保存对象当前状态,并且在之后可以再次恢复到此状态。备忘录模式实现的方式需要保证被保存的对象状态不能被对象从外部访问,目的是为了保护被保存的这些对象状态的完整性以及内部实现不向外暴露。

有时有必要记录一个对象的内部状态。为了允许用户取消不确定的操作或从错误中恢复过来,需要实现检查点和取消机制,而要实现这些机制,你必须事先将状态信息保存在某处,这样才能将对象恢复到它们先前的状态。如何实现这个将状态信息保存在某处呢?使用原型模式?由于对象通常封装了其部分或所有的状态信息,使得其状态不能被其他对象访问,也就不可能在该对象之外保存其状态了。由于原型模式总是返回对象的全部状态信息,同时原型模式使其状态能被其它对象访问,这样就违反了封装的原则,还可能有损应用的可靠性和可扩展性。

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

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

Caretaker:负责保存好备忘录;但是,不能对备忘录的内容进行操作或检查。

备忘录模式是按照以下方式进行协作的:

管理器向原发器请求一个备忘录,保留一段时间后,将其送回给原发器;而有的时候管理者不会将备忘录返回给原发器,因为原发器可能根本不需要退到先前的状态。备忘录是被动的,只有创建备忘录的原发器会对它的状态进行赋值和检索,如下面的时序图:

#include <iostream>
using namespace std;

struct State
{
     wchar_t wcsState[260];
};

class Memento
{
public:
     Memento(State *pState) : m_pState(pState){}

     State *GetState() { return m_pState; }

private:
     friend class Originator;

     State *m_pState;
};

class Originator
{
public:
     Originator() : m_pState(NULL) {}
     ~Originator()
     {
          // Delete the storage of the state
          if (m_pState)
          {
               delete m_pState;
               m_pState = NULL;
          }
     }

     void SetMemento(Memento *pMemento);
     Memento *CreateMemento();

     void SetValue(wchar_t *value)
     {
          memset(wcsValue, 0, 260 * sizeof(wchar_t));
          wcscpy_s(wcsValue, 260, value);
     }

     void PrintState() { wcout<<wcsValue<<endl; }

private:
     State *m_pState; // To store the object's state

     wchar_t wcsValue[260]; // This is the object's real data
};

Memento *Originator::CreateMemento()
{
     m_pState = new State;
     if (m_pState == NULL)
     {
          return NULL;
     }

     Memento *pMemento = new Memento(m_pState);

     wcscpy_s(m_pState->wcsState, 260, wcsValue); // Backup the value
     return pMemento;
}

void Originator::SetMemento(Memento *pMemento)
{
     m_pState = pMemento->GetState();

     // Recovery the data
     memset(wcsValue, 0, 260 * sizeof(wchar_t));
     wcscpy_s(wcsValue, 260, m_pState->wcsState);
}

// Manager the Memento
class Caretaker
{
public:
     Memento *GetMemento() { return m_pMemento; }
     void SetMemnto(Memento *pMemento)
     {
          // Free the previous Memento
          if (m_pMemento)
          {
               delete m_pMemento;
               m_pMemento = NULL;
          }

          // Set the new Memento
          m_pMemento = pMemento;
     }

private:
     Memento *m_pMemento;
};

int main()
{
     Originator *pOriginator = new Originator();
     pOriginator->SetValue(L"On");
     pOriginator->PrintState();

     // Now I backup the state
     Caretaker *pCaretaker = new Caretaker();
     pCaretaker->SetMemnto(pOriginator->CreateMemento());

     // Set the new state
     pOriginator->SetValue(L"Off");
     pOriginator->PrintState();

     // Recovery to the old state
     pOriginator->SetMemento(pCaretaker->GetMemento());
     pOriginator->PrintState();

     if (pCaretaker)
     {
          delete pCaretaker;
     }

     if (pOriginator)
     {
          delete pOriginator;
     }

     return 0;
}

拿《仙剑奇侠传》游戏进行分析,当我们在打大BOSS之前存档,此时就需要将对应的游戏场景,任务信息,人物信息等等状态存储起来;当赢得大BOSS之后,覆盖之前的存档时,就将之前的存档丢弃,新建立一个存档,保存当前的状态信息;如果打输了,恢复存档,就将之前的存档信息读取出来,还原到打大BOSS之前的游戏场景,重新开始打大BOSS。这里面就是使用的备忘录模式。

#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;

     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;
     }

     void Fight()
     {
          m_iBlood -= 100;
          m_iAttack -= 10;
          m_iDefense -= 20;

          if (m_iBlood == 0)
          {
               cout<<"Game Over"<<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();
     pRoleStateCaretaker->SetRoleStateMemento(pLiXY->SaveState());

     // 开始打大BOSS
     pLiXY->Fight();
     pLiXY->ShowState();

     // 读档,从新开始
     pLiXY->RecoveryState(pRoleStateCaretaker->GetRoleStateMemento());
     pLiXY->ShowState();

     return 0;
}

Output:

Blood:100      
Attack:100     
Defense:100    
Game Over      
Blood:0        
Attack:90      
Defense:80     
Recovery...    
Blood:100      
Attack:100     
Defense:100 

1 备忘录模式的使用场景

1.1 需要保存和恢复数据的相关状态场景。如必须保存一个对象在某一个时刻的部分或完整状态,这样以后需要时它才能恢复到先前的状态。

1.2 提供一个可回滚(rollback)的操作。

1.3 数据库连接的事务管理就是用的备忘录模式。

2 备忘录模式的优缺点

2.1 优点

2.1.2 有时一些发起人对象的内部信息必须保存在发起人对象以外的地方,但是必须要由发起人对象自己读取,这时,使用备忘录模式可以把复杂的发起人内部信息对其他的对象屏蔽起来,从而可以恰当地保持封装的边界。

2.1.2 本模式简化了发起人类。发起人不再需要管理和保存其内部状态的一个个版本,客户端可以自行管理他们所需要的这些状态的版本。

2.1.3 当发起人角色的状态改变的时候,有可能这个状态无效,这时候就可以使用暂时存储起来的备忘录将状态复原。

2.2 缺点

2.2.1 如果发起人角色的状态需要完整地存储到备忘录对象中,那么在资源消耗上面备忘录对象会很昂贵。

2.2.2 当负责人角色将一个备忘录存储起来的时候,负责人可能并不知道这个状态会占用多大的存储空间,从而无法提醒用户一个操作是否很昂贵。

2.2.3 当发起人角色的状态改变的时候,有可能这个协议无效。如果状态改变的成功率不高的话,不如采取“假如”协议模式。

3 总结

备忘录模式在实际应用中也不少,如当我们在进行文档编辑时,经常使用的撤销操作。使用C++实现备忘录模式的关键点在于Originator类是Memento的友元类,这样就使得管理备忘录的Caretaker对象,以及其它对象都不能读取、设置备忘录,只有Originator类才能进行备忘录的读取和设置。由于备忘录主要是用于对对象的状态进行备份,实现了撤销操作,如果对象的状态数据很大很多时,在进行备忘时,就会很占用资源,这个是我们在实际开发时需要考虑的东西。结合之前的设计模式,在总结命令模式时,说到命令模式支持事物的回退,而这个就是依靠的备忘录模式来实现的。

-End-

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接