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

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/

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

 

 

你可能感兴趣的:(method)