设计模式 - 备忘录模式

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


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值