代码改变世界

“表驱动”那点事儿。。。Somethings About Table Driven Method

2010-12-16 16:43  凌云健笔  阅读(2684)  评论(8编辑  收藏  举报

1、为什么是“表驱动”?!

  表驱动法,英文为Table driven method,是一种使你可以在表中查找信息,而不必用很多的逻辑语句(if或Case)来把它们找出来的方法。它是一种设计模式,可用来代替复杂的if/else或者switch-case逻辑判断。某种意义上说,任何信息都可以通过“表”来挑选。在简单的情况下,逻辑语句往往更简单而且更直接。但随着逻辑链的复杂,表就变得越来越富有吸引力了。  

 

2、先看个例子。。

  先看baidu 百科中的一个例子。一个比较笨的方法是循环的使用if语句,可以看出本来应该很简单的一件事情,代码却是这么冗余,解决这个的办法就可以用表驱动方法。

1 int iGetMonthDays(int iMonth)
2 {
3 int iDays;
4 if(1 == iMonth)
5 {iDays = 31;}
6 else if(2 == iMonth) {iDays = 28;}
7 else if(3 == iMonth) {iDays = 31;}
8 else if(4 == iMonth) {iDays = 30;}
9 else if(5 == iMonth) {iDays = 31;}
10 else if(6 == iMonth) {iDays = 30;}
11 else if(7 == iMonth) {iDays = 31;}
12 else if(8 == iMonth) {iDays = 31;}
13 else if(9 == iMonth) {iDays = 30;}
14 else if(10 == iMonth) {iDays = 31;}
15 else if(11 == iMonth) {iDays = 30;}
16 else if(12 == iMonth) {iDays = 31;}
17 return iDays;
18 }

  我们可以先定义一个静态数组,这个数组用来保存一年十二个月的天数。用表来更简洁的解决这个问题: 

1 static int aiMonthDays[12] = {31,28,31,30,31,30,31,31,30,31,30,31};   
2  int iGetMonthDays(int iMonth)
3 {
4 return aiMonthDays[(iMonth - 1)];
5 }

 

3、为什么表驱动优于“函数封装或者宏”?
  简短的switch-case或者if/else用起来还是比较顺手的,所以还是继续用吧;但是对于分支太多的长switch-case或者if/else最好能想办法化解开。化解长switch-case的方法有很多种:函数封装?!宏?!还是表驱动?!看下面的代码:

  版本1 - case分支版

1 int ProcessControl(UINT function_no, void* para_in, void* para_out)
2 {
3 int result;
4 switch(function_no)
5 {
6 case PROCESSA:
7 result = ProcessA(para_in,para_out);
8 break;
9 case PROCESSB:
10 result = ProcessB(para_in,para_out);
11 break;
12 case PROCESSC:
13 result = ProcessC(para_in,para_out);
14 break;
15 //..........
16   default:
17 result = UN_DEFINED;
18 break
19 }
20 return result;
21 }
22
23 int ProcessA(void* para_in, void* para_out)
24 {
25 //code....
26 }
27
28 int ProcessB(void* para_in, void* para_out)
29 {
30 //code....
31 }
32
33 int ProcessC(void* para_in, void* para_out)
34 {
35 //code....
36 }

  分支越多,可读性越差,维护起来也越麻烦!看起来也比较不美观、不优雅,离我们优雅的C++代码相差甚远!!考虑一下宏定义。

  版本2 - 宏定义版

1 #define DISPATCH_BEGIN(func) switch(func) \
2 {
3
4 #define DISPATCH_FUNCTION(func_c, function) case func_c: \
5 result = function(para_in,para_out); \
6 break;
7
8 #define DISPATCH_END(code) default: \
9 result = code; \
10 }
11
12
13 int ProcessControl(UINT function_no, void* para_in, void* para_out)
14 {
15 int result;
16
17 DISPATCH_BEGIN(function_no)
18 DISPATCH_FUNCTION(PROCESSA,ProcessA)
19 DISPATCH_FUNCTION(PROCESSB,ProcessB)
20 DISPATCH_FUNCTION(PROCESSC,ProcessC)
21 // .....
22
23 DISPATCH_END(UN_DEFINED)
24
25 return result;
26 }
27 // ProcessA、ProcessB、ProcessC定义同上。。(略)

  用函数封装或者宏取代case块是治标不治本的方法,使用表驱动通常是治疗这种顽症的有效方法。

  版本3 - 表驱动版

1 typedef struct tagDispatchItem
2 {
3 UNIT func_no;
4 ProcessFuncPtr func_ptr;
5 }DISPATCH_ITEM;
6
7 DISPATCH_ITEM dispatch_table[MAX_DISPATCH_ITEM];
8
9 int ProcessControl(UINT function_no, void* para_in, void* para_out)
10 {
11 int i;
12
13 for(i = 0; i < MAX_DISPATCH_ITEM; i++)
14 {
15 if(function_no == dispatch_table[i].func_no)
16 {
17 return dispatch_table[i].func_ptr(para_in,para_out);
18 }
19 }
20 return UN_DEFINED;
21 }

  上述方法中采用的是数组形式;可以换为高级数据结构,也就有了第四个可读性更强、更优雅的版本:

  版本4 - 表驱动版(高级数据结构):

1 // 采用高级数据结构
2 typedef std::hash_map<UINT, ProcessFuncPtr> CmdHandlerMap;
3 CmdHandlerMap HandlerMap;
4
5 void InitHandlerMap()
6 {
7 HandlerMap[PROCESSA] = ProcessFuncPtr(&ProcessA);
8 HandlerMap[PROCESSB] = ProcessFuncPtr(&ProcessB);
9 HandlerMap[PROCESSC] = ProcessFuncPtr(&ProcessC);
10 // .......
11 }
12
13 int ProcessControl(UINT function_no, void* para_in, void* para_out)
14 {
15 CmdHandlerMap::iterator it = HandlerMap.find(function_no);
16
17 if(it!=HandlerMap.end())
18 {
19 ProcessFuncPtr pHandler = it->seceond;
20 return (*pHandler)(para_in,para_out);
21 }
22
23 return UN_DEFINED;
24 }

  使用表驱动的好处就是ProcessControl的代码就这几行,添加新功能,只需要维护驱动表dispatch_table或者HandlerMap就行了,就这样摆脱了冗长乏味的switch-case。


4、研究一下MFC中的表驱动

   也许你会用MFC做相当漂亮的应用程序,也许你认为自己对MFC很熟悉,但是你发现程序中隐藏的表驱动没?你知道他是怎么实现的么?

  在MFC程序声明文件中,你会经常使用这么一句话:DECLARE_MESSAGE_MAP(),而在对应的定义文件中,也会加上这么几句:

1 BEGIN_MESSAGE_MAP(CTestMFCApp, CWinAppEx)
2 ON_COMMAND(ID_APP_ABOUT, &CTestMFCApp::OnAppAbout)
3 ON_COMMAND(ID_FILE_PRINT_SETUP, &CWinAppEx::OnFilePrintSetup)
4 END_MESSAGE_MAP()

  表驱动就隐藏在这些语句的后面,查看DECLARE_MESSAGE_MAP()的宏定义,你会发现:

1 #define DECLARE_MESSAGE_MAP() \
2 protected: \
3 static const AFX_MSGMAP* PASCAL GetThisMessageMap(); \
4 virtual const AFX_MSGMAP* GetMessageMap() const; \

它定义了两个函数,返回值一个AFX_MSGMAP*。这又是什么呢?继续往下搜寻:

1 struct AFX_MSGMAP
2 {
3 const AFX_MSGMAP* (PASCAL* pfnGetBaseMap)();
4 const AFX_MSGMAP_ENTRY* lpEntries;
5 };

这是Window message map,就是我们寻找已久的“表”啊。

  再返回去,看看BEGIN_MESSAGE_MAP(CTestMFCApp, CWinAppEx)和END_MESSAGE_MAP()到底是什么:

1 #define BEGIN_MESSAGE_MAP(theClass, baseClass) \
2 PTM_WARNING_DISABLE \
3 const AFX_MSGMAP* theClass::GetMessageMap() const \
4 { return GetThisMessageMap(); } \
5 const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap() \
6 { \
7 typedef theClass ThisClass; \
8 typedef baseClass TheBaseClass; \
9 static const AFX_MSGMAP_ENTRY _messageEntries[] = \
10 {
11
12 #define END_MESSAGE_MAP() \
13 {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 } \
14 }; \
15 static const AFX_MSGMAP messageMap = \
16 { &TheBaseClass::GetThisMessageMap, &_messageEntries[0] }; \
17 return &messageMap; \
18 } \
19 PTM_WARNING_RESTORE
20
21 #define ON_COMMAND(id, memberFxn) \
22 { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v, \
23 static_cast<AFX_PMSG> (memberFxn) },

把宏替换掉,得到代码:

1 const AFX_MSGMAP* theClass::GetMessageMap() const
2 {
3 return GetThisMessageMap();
4 }
5
6 const AFX_MSGMAP* PASCAL theClass::GetThisMessageMap()
7 {
8 typedef theClass ThisClass;
9 typedef baseClass TheBaseClass;
10 static const AFX_MSGMAP_ENTRY _messageEntries[] =
11 {
12 { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v, static_cast<AFX_PMSG> (memberFxn) },
13 { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v, static_cast<AFX_PMSG> (memberFxn) },
14 { WM_COMMAND, CN_COMMAND, (WORD)id, (WORD)id, AfxSigCmd_v, static_cast<AFX_PMSG> (memberFxn) },
15 //.....
16
17 {0, 0, 0, 0, AfxSig_end, (AFX_PMSG)0 }
18 };
19
20 static const AFX_MSGMAP messageMap =
21 {
22 &TheBaseClass::GetThisMessageMap, &_messageEntries[0]
23 };
24
25 return &messageMap;
26 }

突然之间,豁然开朗!!!


5、总结
  Table-drive 表驱动法是一种替代逻辑语句(if / else)的编程模式,其替代优点是简单清晰,尤其是在判断分支较多的情况时, 另一个显在的优势是可以放在文件中读取,这样一来,就可以就可以不改变源代码前提下更改一些表内容,正合元编程思想。

 

作者: 凌云健笔

出处:http://www.cnblogs.com/lijian2010/

版权:本文版权归作者和博客园共有
转载:欢迎转载,为了保存作者的创作热情,请按要求【转载】
要求:未经作者同意,必须保留此段声明;必须在文章中给出原文连接;否则必究法律责任