问题是这样的,在自己的一个程序里,需要根据接收到的命令(此命令是一个字串)来执行相应的函数。类似的情况可能有:
1、SOCKET 程序:程序可能要根据对方传来的命令来执行相应的函数。
2、脚本解析程序:在一个程序中根据读入的命令串来执行相应的函数。
3、类似WINDOWS的消息系统,一条消息对应一个函数。
反正这种的应用很多,学会这种设计手法是非常有帮助的,因为它可以帮我们轻松的添加新的命令。这种手法也很象MFC 中的消息映射表,但我们当然没必要搞得那么复杂。只要能适合我们的应用就行。好,下面让我们开始吧。
我们想一下,一般碰到这种情况,我们会怎么做。我一般会这样写:
// 注意这里是类成员函数,示例代码仅供参考
// 读脚本
void CParser::GetCmd(CCommandList &CmdList)
{
CString strCmd;
while(!CmdList.End())
{
strCmd = CmdList.GetNextCmd();
ExecCmd(strCmd);
}
}
// 执行脚本
void CParser::ExecCmd(CString strCmd)
{
if(strCmd == "#a")
MethodA();
if(strCmd == "#b")
MethodB();
...
}
这种方法其实也没问题,只不过如果要增加新的命令就要在这个函数后面加,弄不好这个函数就会越来越长了(想一想WINDOWS成百上千的消息)。函数越来越长,
人就会看得好晕,好晕的时候就会出错,要知道,程序的错误大多数是由于人的失误造成的。所以下面是另一种可选的方法:
1、在类的头文件里定义一个map,把命令和函数对应起来,如下(省略了无关的代码)
#pragma warning(disable: 4786)
#include <map>
using namespace std;
class CParser
{
public:
void GetCmd(CCommandList &CmdList);
void ExecCmd(CString strCmd);
// 响应命令表
typedef void (CParser::*FuncPointer)(void);
map<CString, FuncPointer> m_cmdMap;
// 响应函数
void MethodA();
void MethodB();
};
2、在CPP文件里建表,注意写法
CParser::CParser()
{
m_cmdMap["#a"] = (FuncPointer)&CParser::MethodA;
m_cmdMap["#b"] = (FuncPointer)&CParser::MethodB;
}
在这里我们还可以学习MFC的做法,定义一个宏:
#define ON_CMD(a, b) ( m_cmdMap[(a)] = (FuncPointer)&CParser::b )
这样的话,我们的建表就更方便了,如下:
CParser::CParser()
{
ON_CMD("#a", MethodA);
ON_CMD("#b", MethodB);
}
3、修改一下ExecCmd
// 执行脚本
void CParser::ExecCmd(CString strCmd)
{
if(m_cmdMap.find(strCmd) != m_cmdMap.end())
{
FuncPointer func;
// 取得对应函数
func = m_cmdMap[strCmd];
// 请特别注意这里的写法!!!
// 调用函数
(this->*func)();
}
else
{
CString strMsg;
strMsg.Format("找不到命令:/"%s/" 所对应的函数", strCmd);
OutMsg(strMsg);
}
}
这样做我们就实现了自己的命令响应表了。
使用这种方法,好处如下:
1、可以代替上述的if-else、switch-case(例如WINDOWS程序)、虚函数(要知道,这种if-else是可以用多态来代替的)等方法。可参考《代替if-else,switch-case的几种方法》。
2、可以方便添加命令,添加时候不会影响原来的函数,符合OCP原则。
3、各个处理函数分开,不会互相影响,就算一个函数写错了,也不会“一只蚊子搞臭了一锅粥”,把错误的范围控制住了。
可能看到这里,有些人还要问如果参数不同怎么办?这个问题我们在下一篇再讨论。