C++设计模式——备忘录模式

Wesley13
• 阅读 629

备忘录模式

在GOF的《设计模式:可复用面向对象软件的基础》一书中对备忘录模式是这样说的:在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。

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

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

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

UML类图

C++设计模式——备忘录模式

Memento:备忘录存储原发器对象的内部状态。原发器根据需要决定备忘录存储原发器的哪些内部状态;防止原发器以外的其他对象访问备忘录。备忘录实际上有两个接口,管理者只能看到备忘录的窄接口————它只能将备忘录传递给其他对象。相反,原发器能够看到一个宽接口,允许它访问返回到先前状态所需的所有数据。理想的情况是只允许生成备忘录的那个原发器访问本备忘录的内部状态;
Originator:原发器创建一个备忘录,用以记录当前时刻它的内部状态;我们使用备忘录恢复内部状态;
Caretaker:负责保存好备忘录;但是,不能对备忘录的内容进行操作或检查。

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

C++设计模式——备忘录模式

使用场合

在以下情况下使用备忘录模式:

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

代码实现

  1 #include <iostream>
  2 using namespace std;
  3 
  4 struct State
  5 {
  6      wchar_t wcsState[260];
  7 };
  8 
  9 class Memento
 10 {
 11 public:
 12      Memento(State *pState) : m_pState(pState){}
 13 
 14      State *GetState() { return m_pState; }
 15 
 16 private:
 17      friend class Originator;
 18 
 19      State *m_pState;
 20 };
 21 
 22 class Originator
 23 {
 24 public:
 25      Originator() : m_pState(NULL) {}
 26      ~Originator()
 27      {
 28           // Delete the storage of the state
 29           if (m_pState)
 30           {
 31                delete m_pState;
 32                m_pState = NULL;
 33           }
 34      }
 35 
 36      void SetMemento(Memento *pMemento);
 37      Memento *CreateMemento();
 38 
 39      void SetValue(wchar_t *value)
 40      {
 41           memset(wcsValue, 0, 260 * sizeof(wchar_t));
 42           wcscpy_s(wcsValue, 260, value);
 43      }
 44 
 45      void PrintState() { wcout<<wcsValue<<endl; }
 46 
 47 private:
 48      State *m_pState; // To store the object's state
 49 
 50      wchar_t wcsValue[260]; // This is the object's real data
 51 };
 52 
 53 Memento *Originator::CreateMemento()
 54 {
 55      m_pState = new State;
 56      if (m_pState == NULL)
 57      {
 58           return NULL;
 59      }
 60 
 61      Memento *pMemento = new Memento(m_pState);
 62 
 63      wcscpy_s(m_pState->wcsState, 260, wcsValue); // Backup the value
 64      return pMemento;
 65 }
 66 
 67 void Originator::SetMemento(Memento *pMemento)
 68 {
 69      m_pState = pMemento->GetState();
 70 
 71      // Recovery the data
 72      memset(wcsValue, 0, 260 * sizeof(wchar_t));
 73      wcscpy_s(wcsValue, 260, m_pState->wcsState);
 74 }
 75 
 76 // Manager the Memento
 77 class Caretaker
 78 {
 79 public:
 80      Memento *GetMemento() { return m_pMemento; }
 81      void SetMemnto(Memento *pMemento)
 82      {
 83           // Free the previous Memento
 84           if (m_pMemento)
 85           {
 86                delete m_pMemento;
 87                m_pMemento = NULL;
 88           }
 89 
 90           // Set the new Memento
 91           m_pMemento = pMemento;
 92      }
 93 
 94 private:
 95      Memento *m_pMemento;
 96 };
 97 
 98 int main()
 99 {
100      Originator *pOriginator = new Originator();
101      pOriginator->SetValue(L"On");
102      pOriginator->PrintState();
103 
104      // Now I backup the state
105      Caretaker *pCaretaker = new Caretaker();
106      pCaretaker->SetMemnto(pOriginator->CreateMemento());
107 
108      // Set the new state
109      pOriginator->SetValue(L"Off");
110      pOriginator->PrintState();
111 
112      // Recovery to the old state
113      pOriginator->SetMemento(pCaretaker->GetMemento());
114      pOriginator->PrintState();
115 
116      if (pCaretaker)
117      {
118           delete pCaretaker;
119      }
120 
121      if (pOriginator)
122      {
123           delete pOriginator;
124      }
125 
126      return 0;
127 }

我再根据上面的《仙剑奇侠传》来完成备忘录模式,代码如下:

 1 #include <iostream>
 2 using namespace std;
 3 
 4 class RoleStateMemento
 5 {
 6 public:
 7      RoleStateMemento(unsigned iBlood, unsigned iAttack, unsigned iDefense) : m_iBlood(iBlood), m_iAttack(iAttack), m_iDefense(iDefense){}
 8 
 9 private:
10      friend class GameRole;
11 
12      unsigned GetBloodValue() { return m_iBlood; }
13      unsigned GetAttackValue() { return m_iAttack; }
14      unsigned GetDefenseValue() { return m_iDefense; }
15 
16      unsigned m_iBlood;   // 生命力
17      unsigned m_iAttack;  // 攻击力
18      unsigned m_iDefense; // 防御力
19 };
20 
21 class GameRole
22 {
23 public:
24      GameRole() : m_iBlood(100), m_iAttack(100), m_iDefense(100){}
25 
26      // 存档
27      RoleStateMemento *SaveState() { return new RoleStateMemento(m_iBlood, m_iAttack, m_iDefense); }
28 
29      // 恢复存档
30      void RecoveryState(RoleStateMemento *pRoleState)
31      {
32           m_iBlood = pRoleState->GetBloodValue();
33           m_iAttack = pRoleState->GetAttackValue();
34           m_iDefense = pRoleState->GetDefenseValue();
35           cout<<"Recovery..."<<endl;
36      }
37 
38      void ShowState()
39      {
40           cout<<"Blood:"<<m_iBlood<<endl;
41           cout<<"Attack:"<<m_iAttack<<endl;
42           cout<<"Defense:"<<m_iDefense<<endl;
43      }
44 
45      void Fight()
46      {
47           m_iBlood -= 100;
48           m_iAttack -= 10;
49           m_iDefense -= 20;
50 
51           if (m_iBlood == 0)
52           {
53                cout<<"Game Over"<<endl;
54           }
55      }
56 
57 private:
58      unsigned m_iBlood;   // 生命力
59      unsigned m_iAttack;  // 攻击力
60      unsigned m_iDefense; // 防御力
61 };
62 
63 class RoleStateCaretaker
64 {
65 public:
66      void SetRoleStateMemento(RoleStateMemento *pRoleStateMemento) { m_pRoleStateMemento = pRoleStateMemento; }
67      RoleStateMemento *GetRoleStateMemento() { return m_pRoleStateMemento; }
68 
69 private:
70      RoleStateMemento *m_pRoleStateMemento;
71 };
72 
73 int main()
74 {
75      GameRole *pLiXY = new GameRole(); // 创建李逍遥这个角色
76      pLiXY->ShowState(); // 显示初始的状态
77 
78      // 存档
79      RoleStateCaretaker *pRoleStateCaretaker = new RoleStateCaretaker();
80      pRoleStateCaretaker->SetRoleStateMemento(pLiXY->SaveState());
81 
82      // 开始打大BOSS
83      pLiXY->Fight();
84      pLiXY->ShowState();
85 
86      // 读档,从新开始
87      pLiXY->RecoveryState(pRoleStateCaretaker->GetRoleStateMemento());
88      pLiXY->ShowState();
89 
90      return 0;
91 }

总结

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

点赞
收藏
评论区
推荐文章
徐小夕 徐小夕
4年前
《前端实战总结》之设计模式的应用——备忘录模式
概念介绍备忘录模式简单的说就是在不破坏已有逻辑的前提下,将日后需要获取的数据在第一次保存下来以免造成重复且低效的操作。该设计模式最主要的任务就是对现有数据或者
桃浪十七丶 桃浪十七丶
3年前
工厂模式实例(顺便回忆反射机制的应用)
一、原理反射机制的原理JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。工厂模式自述所谓工厂模式,是说由某个产品类接口、产品实现类、工厂类、客户端(单元测试主类)构成的一个模式,大程度的降低了代码的
Stella981 Stella981
3年前
Apache commons chain 初探
Apachecommonschain是什么Apachecommonchain是对责任链设计模式的改造封装,让使用者更加方便的使用。简单回顾一下责任链设计模式在阎宏博士的《JAVA与模式》一书中开头是这样描述责任链(ChainofResponsibility)模式的:责任链模式是一种对象的行为模式。在
Wesley13 Wesley13
3年前
Java序列化(Serializable)与反序列化
序列化是干什么的简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存objectstates,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。什么情况下需要序列化1.当你想把的内存中的对象状态保存到一个文件中
Wesley13 Wesley13
3年前
JAVA设计模式之单例设计模式
    单例模式,是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例。  在JAVA中实现单例,必须了解JAVA内存机制,JAVA中实例对象存在于堆内存中,若要实现单例,必须满足两个条件:  1.限制类实例化对象。即只能产生一个对象。
Wesley13 Wesley13
3年前
Java开发中的23种设计模式详解(转)
设计模式(DesignPatterns)                                 ——可复用面向对象软件的基础设计模式(Designpattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性。毫无疑问,设计模式于己于他
Wesley13 Wesley13
3年前
PHP状态模式
状态设计模式状态模式的作用是允许对象在状态改变时改变其行为对象中频繁的状态非常依赖于条件语句,就自身来说条件语句并没有什么问题,不过,如果选项太多,以至于程序出现混乱,或者增加或改变选项需要太多的是时间。<?php/CreatedbyPhpStorm.User:ge
Wesley13 Wesley13
3年前
22. 状态模式
在状态模式中,我们创建表示各种状态的对象和一个行为随着状态对象改变而改变的context对象。介绍意图:允许对象在内部状态发生改变时改变它的行为,对象看起来好像修改了它的类。主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。何时使用:代码中包含大量与对象状态有关的条件语句
玩转Spring状态机 | 京东云技术团队
说起Spring状态机,大家很容易联想到这个状态机和设计模式中状态模式的区别是啥呢?没错,Spring状态机就是状态模式的一种实现,在介绍Spring状态机之前,让我们来看看设计模式中的状态模式。1\.状态模式状态模式的定义如下:状态模式(StatePat
京东云开发者 京东云开发者
6个月前
玩转Spring状态机
说起Spring状态机,大家很容易联想到这个状态机和设计模式中状态模式的区别是啥呢?没错,Spring状态机就是状态模式的一种实现,在介绍Spring状态机之前,让我们来看看设计模式中的状态模式。1.状态模式状态模式的定义如下:状态模式(StatePatt