设计模式 - 命令模式

回调函数,相信很多人都用过。比如A中定义一个函数,然后A把这个函数指针发给B,B在某个时刻调用这个指针。这就是个回调。其实在过去面向过程语言,比如C语言里面是大量的用到回调函数。就算是现在面向对象的语言,比如JAVA,C#, 有时也还是会用到回调函数。

考虑这么一个例子,有一个加密软件,支持MD5加密和RSA加密。在这个软件的UI界面上有两种方式使用加密:菜单和一个按钮。通过菜单可以对一个文件加密,通过一个按钮也可以加密。使用回调函数机制,我们可以这么做,定义2个函数,比如:Encrypt_MD5() 和Encrypt_RSA()。然后把这2个函数指针传给UI模块,那么当用户通过菜单或者按钮想加密的时候,就可以通过这2个函数指针来调用加密函数。通过回调函数基本可以解决这个问题,但是回调函数是面向过程语言时代的一个产物。现在基本都是OOP时代了,那么在OOP模式下,有什么办法可以替代回调函数呢?可以考虑命令模式。

将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或者记录日志,以及支持可撤销的操作。Command(命令)模式是回调机制的一个面向对象的替代品。

GOF设计模式上是这么说的。先来看看命令模式的结构。

结构

参与者

Command:声明命令操作的接口。

ConcreteCommand:具体执行命令的类。这个类是Command类的子类。通常调用者需要把命令作用的接收者传给Command对象。

Client:创建一个具体命令对象并设定它的接收者。

Invoker: 要求该命令执行这个请求。

Receiver:命令相关的接收者。比如加密例子里面的某个文件,加密函数会对这个文件进行加密。

 

画了一个图来解释基本的调用过程。

基本调用过程

 

代码示例

写一些测试代码来模拟这个过程。

首先定义一个接收者类:

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

定义一个命令基类,里面只有一个纯虚函数Encrypt()。

定义2个CEncryption的子类:

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

protected:
	CMyFile* m_file;
};

class CEncryption_RSA: public CEncryption
{
public:
	CEncryption_RSA(CMyFile* file): m_file(file){}

	virtual void Encrypt()
	{
		cout << "Encrypt file: " << m_file->GetFilePath() << " with RSA\n";
	}

protected:
	CMyFile* m_file;
};

实现具体的加密功能。注意这2个子类都有一个成员来存放接收者。这个例子里面都存储了CMyFile的对象,实际上这里可以根据需要存储不同的接收者。

模拟一个Invoker:

//模拟一个UI类
enum MYCMD{MD5 = 0, RSA};
class CMyUI
{
public:
	void StoreCommand(MYCMD cmdtype, CEncryption* cmd)
	{
		m_mapCmd[cmdtype] = cmd;
	}

	void EncryptFileWithMD5()
	{
		CEncryption* cmd = GetCmd(MD5);
		if (cmd)
		{
			cmd->Encrypt();
		}
	}

	void EncryptFileWithRSA()
	{
		CEncryption* cmd = GetCmd(RSA);
		if (cmd)
		{
			cmd->Encrypt();
		}
	}

	CEncryption* GetCmd(MYCMD cmdtype)
	{
		return m_mapCmd[cmdtype];
	}

protected:
	std::map m_mapCmd;
};

这个invoker里面保存了各种命令对象,用一个std::map来存放。例子假设只有2个命令:MD5加密和RSA加密。

看看如何使用:

void Pattern_Command()
{
	//模拟invoker
	CMyUI ui;

	//创建一个MD5加密命令对象
	CMyFile* file = new CMyFile("c:\\test.txt");
	CEncryption* cmd = new CEncryption_MD5(file);

	//创建一个RSA加密命令对象
	CEncryption* cmd2 = new CEncryption_RSA(file);

	//将命令对象送给invoker。
	ui.StoreCommand(MD5, cmd);
	ui.StoreCommand(RSA, cmd2);

	//模拟调用,比如用户选择MD5加密
	ui.EncryptFileWithMD5();

	//rsa加密
	ui.EncryptFileWithRSA();

	delete cmd;
	delete cmd2;
	delete file;
}

Client会创建命令,然后传给Invoker,那么当用户点击菜单或者按钮的时候,Invoker就可以根据不同的命令找到相应的命令对象,然后调用命令对象的成员函数来实现加密功能。

这是一个最最简单的命令模式例子。那么跟回调函数相比,有什么好处呢?我觉得:

1. 使用了OOP的技术,使得封装性好很多,更加模块化;

2. 可以在命令对象里面保存一些上下文信息,比如支持取消命令等;

3. 扩展新命令比较容易;

4. 可以通过各种命令对象,组合成一个复杂命令,比如混合加密RSA-MD5,先进行RSA加密再进行MD5加密。

 

复杂命令(也有叫宏命令的)

我们已经有了RSA加密命令和MD5加密命令。那么就可以使用这2个命令来实现混合加密RSA-MD5。

只要新增加一个命令类,然后这个命令类的接收者是RSA命令对象和MD5命令对象,如:

//先RSA再MD5
class CEncryption_RSAMD5: public CEncryption
{
public:
	CEncryption_RSAMD5(CEncryption* rsa, CEncryption* md5): cmdRSA(rsa), cmdMD5(md5){}

	virtual void Encrypt()
	{
		cmdRSA->Encrypt();
		cmdMD5->Encrypt();
	}
protected:
	CEncryption* cmdRSA;
	CEncryption* cmdMD5;
};

CEncryption_RSAMD5不再需要CMyFile的对象了,因为rsa和md5命令对象里面已经有这个文件的接收者了。使用例子:

//混合加密
	CEncryption_RSAMD5* cmd3 = new CEncryption_RSAMD5(cmd2, cmd);
	ui.StoreCommand(RSAMD5, cmd3);

	ui.EncryptFileWithRSAMD5();

OK, 组合现有的命令对象还是蛮简单的。

仔细看看复杂命令(宏命令)的实现,是不是感觉很熟悉?CEncryption_RSA, CEncryption_MD5和CEncryption_RSAMD5都继承与CEncryption,然后CEncryption_RSAMD5又使用了CEncryption_RSA和CEncryption_MD5的实例。也就是说CEncryption_RSAMD5是由CEncryption_RSA和CEncryption_MD5组成的。这不就是组合模式吗?是的,没错,通常宏命令是用结构型模式里面的Composite(组合)模式来实现的。

 

取消命令

上面的例子没有考虑取消命令的情况。其实在命令模式里面是可以支持取消命令的。我们知道MD5是一种不可逆的加密算法。那么MD5加密后,怎么再得到原来的内容呢?解密显然不行,因为MD5不可逆。我们可以考虑支持取消命令。比如在MD5加密前,保存原始数据,然后在加密。数据可以考虑保存在MD5命令对象里面或者Invoker里面,具体视情况而定。至于怎么保存,有很多方法,这里不再探讨。其实我个人感觉保存现场来支持取消命令,并不是那么容易,因为有时数据保存还是有难度的。但是起码命令模式给取消命令提供了一种可能。


最后:
个人感觉,当回调函数大量被使用的时候,可以考虑命令模式,看看是否能给我们带来好处。还是那句话,不要死搬硬套设计模式,这样反而适得其反。在引入一个设计模式前,先多想想是否有必要。
 

 付完整代码:

#pragma once

#include 
#include 
#include 

using namespace std;

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

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

protected:
	CMyFile* m_file;
};

class CEncryption_RSA: public CEncryption
{
public:
	CEncryption_RSA(CMyFile* file): m_file(file){}

	virtual void Encrypt()
	{
		cout << "Encrypt file: " << m_file->GetFilePath() << " with RSA\n";
	}

protected:
	CMyFile* m_file;
};

//先RSA再MD5
class CEncryption_RSAMD5: public CEncryption
{
public:
	CEncryption_RSAMD5(CEncryption* rsa, CEncryption* md5): cmdRSA(rsa), cmdMD5(md5){}

	virtual void Encrypt()
	{
		cmdRSA->Encrypt();
		cmdMD5->Encrypt();
	}
protected:
	CEncryption* cmdRSA;
	CEncryption* cmdMD5;
};

//模拟一个UI类
enum MYCMD{MD5 = 0, RSA, RSAMD5};
class CMyUI
{
public:
	void StoreCommand(MYCMD cmdtype, CEncryption* cmd)
	{
		m_mapCmd[cmdtype] = cmd;
	}

	void EncryptFileWithMD5()
	{
		CEncryption* cmd = GetCmd(MD5);
		if (cmd)
		{
			cmd->Encrypt();
		}
	}

	void EncryptFileWithRSA()
	{
		CEncryption* cmd = GetCmd(RSA);
		if (cmd)
		{
			cmd->Encrypt();
		}
	}

	void EncryptFileWithRSAMD5()
	{
		CEncryption* cmd = GetCmd(RSAMD5);
		if (cmd)
		{
			cmd->Encrypt();
		}
	}

	CEncryption* GetCmd(MYCMD cmdtype)
	{
		return m_mapCmd[cmdtype];
	}

protected:
	std::map m_mapCmd;
};

调用:

void Pattern_Command()
{
	//模拟invoker
	CMyUI ui;

	//创建一个MD5加密命令对象
	CMyFile* file = new CMyFile("c:\\test.txt");
	CEncryption* cmd = new CEncryption_MD5(file);

	//创建一个RSA加密命令对象
	CEncryption* cmd2 = new CEncryption_RSA(file);

	//将命令对象送给invoker。
	ui.StoreCommand(MD5, cmd);
	ui.StoreCommand(RSA, cmd2);

	//模拟调用,比如用户选择MD5加密
	ui.EncryptFileWithMD5();

	//rsa加密
	ui.EncryptFileWithRSA();

	//混合加密
	CEncryption_RSAMD5* cmd3 = new CEncryption_RSAMD5(cmd2, cmd);
	ui.StoreCommand(RSAMD5, cmd3);

	ui.EncryptFileWithRSAMD5();

	delete cmd;
	delete cmd2;
	delete cmd3;
	delete file;
}



 

 

你可能感兴趣的:(设计模式,command,命令模式,pattern)