Memento pattern,备忘录模式。不知道哪位老兄第一个翻译的,翻译的真形象。备忘录,就是将某个对象的某个时刻的状态保存下来,以后可以继续查看,还可以恢复状态。
意图
在不破坏封装性的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可将该对象恢复到原先保存的状态。
结构
从这个结构图可以看到,Originator会创建一个备忘录,然而这个备忘录不是保存在Originator里面,而是保存在其他地方Caretaker。Originator可以向Memento存取信息,这个Memento只对Originator可见,其他对象并不知道这个memento。这样可以减少Memento的状态被误操作的可能。
通常,在C++里面,我们可以将Memento的状态存取函数设置为私有的,同时将Originator设置为Memento的友元。这样就只有Originator可以存取Memento的状态,其他对象都不行。
优点:
1. 保持原发器的封装性,原发器可以将状态信息主动的保存在备忘录里面,这样就不需要公共这些信息。只有原发器和备忘录知道这些信息。
2. 简化了原发器,原发器不需要在管理状态数据的存储问题,这个问题交给了备忘录。
缺点:
上述优点2既是优点,也是确定。优点是简化了原发器,缺点是有时可能需要拷贝大量的信息,这也是一个开销。状态数据越多,这个问题越严重。所以如果原发器里面的状态数据开销很大,那么可能不适用备忘录模式。因为拷贝状态数据的开销可能会很大,这样就坏处>好处了。倒不如直接在原发器里面保存状态数据。
备忘录模式流程
画了一个图来描述备忘录模式的主要过程。
总体来讲,调用过程还是蛮简单的。我觉得备忘录模式的要点是:
原发器(Originator)需要导出2个函数:CreateMemento和SetMemento。
CreateMemento()就是将原发器某个时刻的状态信息保存到备忘录里面,SetMemento就是恢复数据。
我们前面讲命令模式的时候(http://blog.csdn.net/zj510/article/details/8486120),有个问题只是提了一下,没有细讲。那就是如何取消操作。取消操作就需要在操作之前保存一些信息。有很多方法可以做到这一点,其中有一种办法就是备忘录模式。考虑命令模式里面的例子,MD5加密是不可逆的,那么我们如何支持取消MD5加密呢?可以考虑备忘录模式,在MD5加密前,原发器(加密类)先把文件内容保存到备忘录对象。这样我们想取消MD5加密的时候,只需要将备忘录里面的数据读出来就可以了。
继续沿用加密的例子。为了突出备忘录模式,我把之前的加密例子简单到只有MD5加密。
给MD5加密类增加一个相应的备忘录类,如:
class CEncryptionMemento
{
private:
friend class CEncryption_MD5;
void SetContent(const string& content)
{
m_content = content;
}
const string& GetContent(){return m_content;}
private:
string m_content;
};
这个备忘录类之保存一个信息,就是文件内容。注意备忘录类只导出了两个函数,并且这2个函数是私有的,同时指定CEncryption_MD5为友元。这样就只有CEncryption_MD5类可以存取状态信息。
看看Originator类,
class CEncryption
{
public:
virtual void Encrypt() = 0;
virtual CEncryptionMemento* CreateMemento() = 0;
virtual void SetMemento(CEncryptionMemento* memento) = 0;
};
class CEncryption_MD5: public CEncryption
{
public:
CEncryption_MD5(CMyFile* file): m_file(file){}
virtual void Encrypt()
{
cout << "Encrypt file: " << m_file->GetFilePath() << " with MD5\n";
}
CEncryptionMemento* CreateMemento()
{
CEncryptionMemento* memento = new CEncryptionMemento();
//假设文件内容是hello world
cout << "read content of file: " << m_file->GetFilePath() << ", and save the content into memento\n";
memento->SetContent("hello world");
return memento;
}
void SetMemento(CEncryptionMemento* memento)
{
cout <<"save the content in memento into file:" << m_file->GetFilePath() <<"\n";
}
protected:
CMyFile* m_file;
};
其他都和命令模式里面的例子一样,看关键的两个函数CreateMemento和SetMemento。
CreateMemento创建一个备忘录,读取当前文件的数据,并且保存到备忘录。注意这个备忘录对象并不是保存在原发器(加密类)中的。
SetMemento有一个参数是备忘录,也就是将在其他地方保存的备忘录对象传进来,根据这个备忘录对象恢复数据。
再看看caretaker怎么调用备忘录
//模拟一个UI类
class CMyUI
{
public:
CMyUI()
{
CMyFile* file = new CMyFile("c:\\test.txt");
m_cmd = new CEncryption_MD5(file);
}
void EncryptFileWithMD5()
{
if (m_cmd)
{
m_memento = m_cmd->CreateMemento();//在加密前先保存一些数据,以便后面可以取消加密。
m_cmd->Encrypt();
}
}
void UndoEncrypt()
{
m_cmd->SetMemento(m_memento);
}
protected:
CEncryption* m_cmd;
CEncryptionMemento* m_memento;
};
可以看到在加密前,先调用Originator::CreateMemento()来创建并保存当前状态信息。然后在undo的时候,将保存的备忘录对象传给Originator。这样Originator就可以根据备忘录对象恢复数据了。
总结
个人觉得备忘录模式还是蛮有用的。但是使用备忘录模式的时候,一定要注意拷贝状态对象的开销是否很大,如果从Originator拷贝状态信息到Memento的开销很大,那么就要想想引入备忘录模式是否合适。
附:
所有代码:
#pragma once
#include <string>
#include <iostream>
#include <map>
using namespace std;
class CEncryptionMemento
{
private:
friend class CEncryption_MD5;
void SetContent(const string& content)
{
m_content = content;
}
const string& GetContent(){return m_content;}
private:
string m_content;
};
class CMyFile
{
public:
CMyFile(const std::string& strFile): m_strFile(strFile){}
const std::string& GetFilePath() const{return m_strFile;}
protected:
std::string m_strFile;
};
class CEncryption
{
public:
virtual void Encrypt() = 0;
virtual CEncryptionMemento* CreateMemento() = 0;
virtual void SetMemento(CEncryptionMemento* memento) = 0;
};
class CEncryption_MD5: public CEncryption
{
public:
CEncryption_MD5(CMyFile* file): m_file(file){}
virtual void Encrypt()
{
cout << "Encrypt file: " << m_file->GetFilePath() << " with MD5\n";
}
CEncryptionMemento* CreateMemento()
{
CEncryptionMemento* memento = new CEncryptionMemento();
//假设文件内容是hello world
cout << "read content of file: " << m_file->GetFilePath() << ", and save the content into memento\n";
memento->SetContent("hello world");
return memento;
}
void SetMemento(CEncryptionMemento* memento)
{
cout <<"save the content in memento into file:" << m_file->GetFilePath() <<"\n";
}
protected:
CMyFile* m_file;
};
//模拟一个UI类
class CMyUI
{
public:
CMyUI()
{
CMyFile* file = new CMyFile("c:\\test.txt");
m_cmd = new CEncryption_MD5(file);
}
void EncryptFileWithMD5()
{
if (m_cmd)
{
m_memento = m_cmd->CreateMemento();//在加密前先保存一些数据,以便后面可以取消加密。
m_cmd->Encrypt();
}
}
void UndoEncrypt()
{
m_cmd->SetMemento(m_memento);
}
protected:
CEncryption* m_cmd;
CEncryptionMemento* m_memento;
};
#include "Memento.hpp"
void Pattern_Memento()
{
CMyUI* test = new CMyUI();
test->EncryptFileWithMD5();
test->UndoEncrypt();
}