一雨田的专栏

伟人将复杂的事情变简单,小人将简单的事情变复杂

原创 实现自己的“命令映射表”(下)收藏

新一篇: 设计模式系列代码下载--VC++可编译工程 | 旧一篇: 实现自己的“命令映射表”(上)


在上一篇中,我们讨论了如何设计一个“命令映射表”,但是上篇对应于表中“命令”的所有函数都是同一类型的,均为返回值为 void,参数为 void 的函数。

如果我们要根据不同的“命令”来执行不同类型的函数要怎么办呢? (这里的不同指的是执行函数的返回值可能不同,参数列表可能不同。)。好,我们下面就开始讨论这个问题。

可能这篇比上篇会难一点,拿出你程序员的毅力来。想想搞定了以后,我们就可以在程序中使用这种手法,可以明白MFC的消息映射表机制(其实这里非常类似于MFC的消息映射机制),还可以拓展自己的设计思路,提高自己的设计水平。最重要的是,使用它能够为你的程序带来好处。OK, Let's GO!

XXX.H 文件

1、首先,要把 "命令" 和 "函数" 对应起来,map是必不可少的,函数指针也是不能少的,而命令是 CString 类型的,所以我们在类里写下这两个东西。

如下:

class CParser
{
 ...

public:
 typedef void (CParser::*FuncPointer)(void);
 map<CString, FuncPointer> m_cmdMap;
 
 ...
}

2、我们现在是要执行不同类型的函数,上面的函数指针只是一种类型(返回值为void, 参数列表为void)。

所以,这里要定义各种类型的函数指针。

而且还需要一个enum结构来标示我们需要的函数类型。(因为函数指针都是4个字节的,可以说是无类型的,我们要另外定义类型来标示它,以便能判断选择),好,思路有了,赶快写下来:

// 要前置声明一下,因为下面这些代码不写在类里了,写在类的外头/前面
class CParser;

typedef void (CParser::*FuncPointer)(void);

// 定义一个 union 结构来装各种的函数指针

// 实际这里是使用 union 来做转换
// 因为函数指针在 union CmdFunctions 中定义,都是 4 bytes,
// 所以利用 union 的特性可顺利做转换
union CmdFunctions
{
 FuncPointer pfn;
 
 void (CParser::*pfn_vs)(CString&);  // 返回值为 void, 参数为 CString,注意pfn_vs的命名,表示返回值为void,参数为CString
 void (CParser::*pfn_vsi)(CString&, int); // 返回值为 void, 参数为 CString, int 注意命名,同上。
};

// 定义一个 enum 结构来标示我们的函数类型
enum FUNC_TYPE
{
 FUCN_TYPE_VOID_STRING,   // 函数类型:返回值为void,参数为:string
 FUCN_TYPE_VOID_STRING_INT  // 函数类型:返回值为void,参数为:string, int
};

3、好了,写下了上面两个关键的东西,我们要考虑一下,如何使用map把命令和函数指针对应起来了。

由于STL map只有两个参数,所以,我就要自定义一个结构放进map的第二个参数中去,这个结构包括了函数指针,函数类型,那我们下面就可以根据命令找到这个结构体,然后根据结构体中的函数类型调用相应的函数指针了。嗯,写下来:


// 在类的外头再定义一个结构体
typedef struct FunctionInfo
{
 enum FUNC_TYPE funcType; // 函数类型
 FuncPointer pfn;  // 函数指针

}FuncInfo;


// 在类里
class CParser
{
 ...

public:
 map<CString, FuncInfo> m_cmdMap; // map 改成这个样子了
 
 ...
}

好了,该定义的变量、结构等等的都搞定了,我们可以到  CPP 文件建立表和尝试利用命令来调用函数了。

XXX.CPP 文件

1、首先建表,在构造函数中

CParser::CParser()
{
 FuncInfo fi;
  
 // 下面要注意函数类型和函数指针类型

 fi.funcType = FUCN_TYPE_VOID_STRING;
 fi.pfn = (FuncPointer) ( void(CParser::*)(CString&) ) &CParser::MethodA;
 m_cmdMap["#a"] = fi;

 fi.funcType = FUCN_TYPE_VOID_STRING_INT;
 fi.pfn = (FuncPointer) ( void(CParser::*)(CString&, int) ) &CParser::MethodB;
 m_cmdMap["#b"] = fi;
}

由于map的限制,这里很难写成宏,可以写成函数,稍微简化一下。在MFC中是对每个不同的 ON_XXX 宏作不同的函数指针类型对应,这里不展开说明。
我们写一个函数来简化和统一建表操作。

void CParser::SetCmdMap(LPCTSTR strCmd, enum FUNC_TYPE ft, FuncPointer pfn)
{
 FuncInfo fi; 
 fi.funcType = ft;
 fi.pfn = pfn;
 m_cmdMap[strCmd] = fi; 
}

那我们的建表就可以简化成:

CParser::CParser()
{
 SetCmdMap("#a", FUCN_TYPE_VOID_STRING,  (FuncPointer) ( ( void(CTestCmdMapDlg::*)(CString&) ) &CParser::MethodA ) );
 SetCmdMap("#b", FUCN_TYPE_VOID_STRING_INT, (FuncPointer) ( ( void(CTestCmdMapDlg::*)(CString&, int) ) &CParser::MethodB ));
}

2、根据命令找到相应的函数并执行

我们下面就可以根据命令来寻找表中对应的函数了。代码如下,和上篇的代码差不多,就是多了个函数指针类型的判断选择 switch-case。

void CParser::ExecCmd(CString &strCmd, CString &strParam)
{
 if(m_cmdMap.find(strCmd) != m_cmdMap.end())
 {
  enum FUNC_TYPE ft;

  // 由于函数指针在 union CmdFunctions 中定义,都是 4 bytes,
  // 所以利用 union 的特性可顺利做转换 
  union CmdFunctions cfs;  

  ft = m_cmdMap[strCmd].funcType;
  cfs.pfn = m_cmdMap[strCmd].pfn;
  
  switch(ft)
  {
  case FUCN_TYPE_VOID_STRING:
   (this->*cfs.pfn_vs)(strParam);  // 利用 union 的特性转换
   break;
  case FUCN_TYPE_VOID_STRING_INT:
   (this->*cfs.pfn_vsi)(strParam, 1); // 利用 union 的特性转换
   break;
  default:
   AfxMessageBox("没有定义函数类型。");
   break;
  }
 }
 else
 {
  CString strFmt;
  strFmt.Format("找不到\"%s\"对应的函数。", strCmd);
  AfxMessageBox(strFmt);
 }
}

3、写上对应的函数

void CParser::MethodA(CString &strParam)
{
 AfxMessageBox(strParam);
}

void CParser::MethodB(CString &strParam, int nParam)
{
 CString strFmt;
 strFmt.Format("%s,  %d", strParam, nParam);
 AfxMessageBox(strFmt);
}

4、写个函数测试一下这个表

void CParser::OnButton1()
{
 ExecCmd(CString("#a"), CString("123"));
 ExecCmd(CString("#b"), CString("456"));
}

经过第4步的测试,显然结果是正确的。到这里,我们实现了更高级的“命令映射表”,可以执行不同类型的函数了。

看到这里,希望你能明白这种设计方法。我也累了。。。。。。

上面的代码可能不完整,我写了个简单完整的 MFC对话框 程序来演示这个设计,需要的朋友可以在以下地址下载:

http://www.cppblog.com/Files/dylgsy/TestCmdMap.rar

发表于 @ 2006年09月21日 12:08:00|评论(loading...)|编辑

新一篇: 设计模式系列代码下载--VC++可编译工程 | 旧一篇: 实现自己的“命令映射表”(上)

评论

#不来了 发表于2006-09-21 17:34:00  IP: 61.144.34.*
下面使用java来实现“命令映射表”,在jdk1.5下可运行,大家猜猜输出是啥。(一只手把这段代码敲出来可真tmd累。。。。)
import java.lang.reflect.InvocationTargetException;


public class Test {
public static void main(String[] args)
{
Test t = new Test();
MethodRepository mr = new MethodRepository();
java.lang.reflect.Method m1 = t.getTheMethod("m1");
java.lang.reflect.Method m2 = t.getTheMethod("m2",Integer.class);

try {
m1.invoke(mr);
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}



try {
System.out.print(m2.invoke(mr, new Integer(999)));
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (InvocationTargetException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
java.lang.reflect.Method getTheMethod(String mName,Class ... parameterTypes)
{
Class c=null;
try {
c = java.lang.Class.forName("MethodRepository");
} catch (ClassNotFoundException
#teli_eurydice 发表于2006-09-23 00:00:00  IP: 221.234.209.*
你好,很不错,不过用命令模式可以更加清晰一点,有兴趣可以一起研究 teli_eurydice@163.com
#winks(少爷) 发表于2006-09-26 09:29:00  IP: 211.144.196.*
不错不错,很好,谢谢:)
#sankt 发表于2007-01-30 08:18:05  IP: 218.4.63.*
不错,要好好学习
#sankt 发表于2007-01-30 08:20:26  IP: 218.4.63.*
不错,要好好学习
#kyo_zy 发表于2007-07-04 15:02:44  IP: 203.86.89.*
的确很好,要是有c#方面的就更好,呵呵
#sstudent 发表于2007-10-16 18:10:44  IP: 124.16.129.*
不过如果函数的类型也是一个变化因素,经常要在map上添加一些不同类型的函数指针的话,函数指针类型的判断选择 switch-case的代码要经常增加,同样会存在维护的问题。这种问题c++好像没有什么好的解决办法。
#xiaoyvr 发表于2008-06-19 02:10:08  IP: 125.33.175.*
重载()操作符,把函数封装成对象就OK了(不重载也可以,反正要把参数、返回值什么的封装成对象)。

所有问题归根结底都似乎需要用工厂来解决……
发表评论  


登录
Csdn Blog version 3.1a
Copyright © 一雨田