C++设计模式——备忘录模式(Memento Pattern)
微信公众号:幼儿园的学霸
目录
定义
Without violating encapsulation, capture and externalize an object’s internal state so that the object can be restored to this state later
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态
备忘录模式:保存一个对象的某个状态,以便在适当的时候恢复对象。备忘录模式属于行为型模式。
备忘录模式主要应用于备份或者回退操作,为了使软件使用更友好,通常都有回退功能,软件一般也要提供回退机制,而要实现回退,就必须事先备份好状态信息,所以有了备忘录模式就有实现系统回退到某一个特定的历史状态。
备忘录对象用于存储另外一个对象内部状态的快照对象,所以备忘录模式又可以称之为快照模式(Snapshot Pattern)或Token模式
其UML类图如下所示:
主要角色如下:
- 备忘录角色(Memento): 负责存储发起人的内部状态,在需要的时候提供这些内部状态给发起人;
- 发起人角色(Originator): 记录当前时刻的内部状态信息,提供创建备忘录和恢复备忘录数据的功能,可以根据需要决定备忘录角色中保存自身的哪些内部状态,能够访问备忘录里的所有信息;
- 管理者角色(Caretaker): 对备忘录进行管理,提供保存与获取备忘录的功能,但其不能对备忘录的内容进行访问与修改,只能够将备忘录传递给其他对象。
代码示例
游戏存档为例,看一下如何用备忘录模式实现
#include <bits/stdc++.h>
//
//备忘录模式
//关键代码:Memento类、Originator类、Caretaker类;
// Originator类不与Memento类耦合,而是与Caretaker类耦合
//
//需要保存的信息
//此处将需要保存的信息提取出来放在一个结构体中
//也可以不用放在结构体中,直接作为类的成员变量
typedef struct {
int nHp;//血量
int nMp;//蓝量
int nAttack;//攻击
}GameValue;
//备忘录类
class Memento{
public:
Memento(const GameValue& gameValue):m_gameValue(gameValue){}
//函数前后均加const
//前const:返回的变量不可修改
//后const:该函数readonly,表示该类的this指针为const类型,
// 不能改变类的成员变量的值
const GameValue& getValue() const{
return m_gameValue;
}
//Memento() = delete;
//Memento(const Memento &) = delete;
//Memento &operator=(const Memento &) = delete;
private:
GameValue m_gameValue;
};
//管理者角色
class Caretaker
{
public:
const std::shared_ptr<Memento> GetState(const std::string& key) {
return mData[key];
}
void SetState(const std::string& key,
std::shared_ptr<Memento> pMemento) {
mData[key] = pMemento;
}
private:
//此处考虑在map中存储指针,而非直接存储对象
std::map<std::string,std::shared_ptr<Memento>> mData;
};
//发起人角色
class Hero{
public:
Hero()
:m_gameValue{100,100,100}{
}
Hero(const GameValue& gameValue)
:m_gameValue(gameValue){
}
//保存当前信息
void saveState(const std::string& key) {
m_Caretaker.SetState(key, std::make_shared<Memento>(m_gameValue));
}
//恢复信息
const void resumeState(const std::string& key) {
m_gameValue = m_Caretaker.GetState(key)->getValue();
}
void battle()
{
m_gameValue.nHp = rand()%100;
m_gameValue.nMp = rand()%100;
m_gameValue.nAttack = rand()%100;
}
void showState() {
std::cout<<"血量:"<<m_gameValue.nHp<<std::endl
<<"蓝量:"<<m_gameValue.nMp<<std::endl
<<"攻击:"<<m_gameValue.nAttack<<std::endl;
}
private:
GameValue m_gameValue;
Caretaker m_Caretaker;//常规对象,没有必要用指针
};
int main()
{
Hero hero;
std::cout<<"----战斗前-----"<<std::endl;
hero.showState();
std::cout<<std::endl;
hero.saveState("战斗前");
hero.battle();
std::cout<<"-----战斗1后-----"<<std::endl;
hero.showState();
std::cout<<std::endl;
hero.saveState("战斗1后");
hero.battle();
std::cout<<"-----战斗2后-----"<<std::endl;
hero.showState();
hero.saveState("战斗2后");
std::cout<<std::endl;
hero.battle();
std::cout<<"-----战斗3后-----"<<std::endl;
hero.showState();
hero.saveState("战斗3后");
std::cout<<std::endl;
hero.resumeState("战斗1后");
std::cout<<"-----恢复战斗1结果-----"<<std::endl;
hero.showState();
std::cout<<std::endl;
hero.resumeState("战斗前");
std::cout<<"-----恢复战斗前-----"<<std::endl;
hero.showState();
std::cout<<std::endl;
return 0;
//运行结果如下:
//----战斗前-----
//血量:100
//蓝量:100
//攻击:100
//
//-----战斗1后-----
//血量:83
//蓝量:86
//攻击:77
//
//-----战斗2后-----
//血量:15
//蓝量:93
//攻击:35
//
//-----战斗3后-----
//血量:86
//蓝量:92
//攻击:49
//
//-----恢复战斗1结果-----
//血量:83
//蓝量:86
//攻击:77
//
//-----恢复战斗前-----
//血量:100
//蓝量:100
//攻击:100
}
总结
-
备忘录模式与原型模式:
原型模式也能保存一个对象在某一个时刻的状态,那么两者有何不同的呢?原型模式保存的是当前对象的所有状态信息,恢复的时候会生成与保存的对象完全相同的另外一个实例;而备忘录模式保存的是我们关心的在恢复时需要的对象的部分状态信息,相当于快照。 -
优点: 1.给用户提供了一种可以恢复状态的机制,可以使用户能够比较方便地回到某个历史的状态; 2.实现了信息的封装,使得用户不需要关心状态的保存细节。
-
缺点:消耗资源。如果类的成员变量过多,势必会占用比较大的资源,而且每一次保存都会消耗一定的内存。
-
使用场景: 1、需要保存/恢复数据的相关状态场景;2、提供一个可回滚的操作。
-
应用实例: 1.Windows 里的 ctri + z;2.浏览器的后退;3.虚拟机的快照与恢复;4.git版本管理;5.棋牌游戏的悔牌操作。
参考资料
1.备忘录模式(详解版)
2.备忘录模式(Memento)C++实现