如何用c++表驱动替换if/else和switch/case语句
C++的表驱动法
目的:使用表驱动法,替换复杂的if/else和switch/case语句。
一、常用示例
以switch为例,常用示例如下:
Funcition() { switch (key) { case key1: statements 1; break; case key2: statements 2; break; ... case keyn: statements n; break; default: break; } }
上述switch代码段,实际集成了3种类型逻辑:
1. 实现关键字的处理代码;
2. 将关键字与处理代码关联;
3. 以关键字选择分支,执行处理代码;
我们将在一个switch代码中维护3种变化。将1的处理代码段,模块化为函数后,变化点减少为2个。
在分支增加到几十个时,代码维护性变得很差;而且switch对非整数类型无能为力。
二、表驱动法
做法:
1. 将变化点2,做成一个[关键字:处理函数]映射结构(推荐map容器),在独立函数中赋值。
2. 将变化点3,做成一个查找关键字,执行对应函数的简单函数
好处:
1. 独立出“选择分支”变化点,变为固定的处理流程。
2. 独立出“关键字和处理函数的关联”,易于维护。
限制条件:
1. 处理函数类型一样(这在C++中不成问题);
2. 处理函数简单,但每个函数有差异(如果处理较为复杂,请使用创建型设计模式)
扩展:
1. 对于处理函数由符合条件分支情况,变化点2使用list结构,按优先级关联处理函数,使用 “职责链”形式的表驱动法。
三、C++实现注意
代码:
// 3个文件,Client.cpp, TableDrave.h, TableDrive.cpp // vvvvv Client.cpp begin // ------------------------------------------------------------ // Name : Client.cpp // Description : 调用接口 // History : // ------------------------------------------------------------ #include "TableDrive.h" // ------------------------------------------------------------ int main() { TableDrive test; test.HandleKeyword(KEYWORD_A); test.HandleKeyword(KEYWORD_B); test.HandleKeyword(KEYWORD_C); test.HandleKeyword(KEYWORD_START); test.HandleKeyword(KEYWORD_D); return 0; } // ^^^^^ Client.cpp end // vvvvv TableDrive.h begin // ------------------------------------------------------------ // Name : TableDrive.h // Description : 表驱动头文件 // History : // ------------------------------------------------------------ #ifndef _TEST_DRIVE_H #define _TEST_DRIVE_H #include <map> // ------------------------------------------------------------ // 测试用关键字 enum KEYWORD { KEYWORD_START = -1, KEYWORD_A = 0, KEYWORD_B, KEYWORD_C, KEYWORD_D, KEYWORD_END, }; // ------------------------------------------------------------ // 可以使用 std:: 单个引用 using namespace std; class TableDrive { public: // ------------------------------------------------------------ // Description : // 根据关键字,执行处理函数 // Parameters : // string keyword,关键字 // Return Value : // bool,true,函数执行成功,false,找不到键字对应的函数,或函数执行失败 // Errors : // 无 // ------------------------------------------------------------ bool HandleKeyword(int keyword); // ------------------------------------------------------------ // Description : // 关联关键字到处理函数 // Parameters : // 无 // Return Value : // bool,true,正常,false,异常 // Errors : // 无 // ------------------------------------------------------------ bool MapKeyToHandle(); TableDrive(); ~TableDrive(); private: // vv 处理函数,true,执行成功,false,执行失败 bool HandleKeyA(); bool HandleKeyB(); bool HandleKeyC(); // ^^ private: // :TRICKY: 成员函数指针定义 typedef bool (TableDrive:: *PHandle)(void); map<int, PHandle> m_KeyToHandle; // 关键字对应处理函数 }; #endif // ^^^^^ TableDrive.h end // vvvvv TableDrive.cpp begin // ------------------------------------------------------------ // Name : TableDrive.cpp // Description : 表驱动实现文件 // History : // ------------------------------------------------------------ #include <stdio.h> #include "TableDrive.h" // ------------------------------------------------------------ // 根据关键字,执行处理函数 bool TableDrive::HandleKeyword(int keyword) { typedef map<int, PHandle>::const_iterator CI; CI iter = m_KeyToHandle.find(keyword); // 没有搜索到关键字 if (m_KeyToHandle.end() == iter) { printf("\n @@ search Keyword %d fail!\n", keyword); return false; } // :TRICKY: 注意成员函数指针的引用格式 PHandle pFunction = iter->second; return (this->*pFunction)(); } TableDrive::TableDrive() { printf("\n vv TableDrive::TableDrive()\n"); MapKeyToHandle(); } TableDrive::~TableDrive() { printf("\n ^^ TableDrive::~TableDrive()\n"); } // ------------------------------------------------------------ // 关联关键字到处理函数 bool TableDrive::MapKeyToHandle() { m_KeyToHandle[KEYWORD_A] = &TableDrive::HandleKeyA; m_KeyToHandle[KEYWORD_B] = &TableDrive::HandleKeyB; m_KeyToHandle[KEYWORD_C] = &TableDrive::HandleKeyC; return true; } // 处理函数 A bool TableDrive::HandleKeyA() { printf("\n ** A, HandleKeyA()\n\n"); return true; } bool TableDrive::HandleKeyB() { printf("\n ** B, HandleKeyB()\n\n"); return true; } bool TableDrive::HandleKeyC() { printf("\n ** C, HandleKeyC()\n\n"); return true; } // ^^^^^ TableDrive.cpp end
关注点:
主要关注3个点,维护第2、3点
1. HandleKeyword(),根据关键字,执行处理函数。固定后基本不改变;
2. MapKeyToHandle(),关联关键字到处理函数;
3. Handle(),各个处理函数
成员函数指针使用注意
1. 声明格式,与C相比,函数指针前要包含类域;
typedef bool (TableDrive:: *PHandle)();
2. 声明位置,包含在类中,否则不能识别类域标志;
3. 赋值语法格式,与C相比,函数指针前要包含类域;
PHandle pFunction = &TableDrive::HandleKeyA;
4. 调用语法格式,与C相比,需要加上this,并以强制解引用方式调用;
(this->*pFunction)();
四、实用案例
1. 菜单调节。一个模块,有几十个菜单参数可以调节,每个菜单调节的步进、范围不同,但都是“触发消息、调节数值”流程。
2. 按键响应。多个按键,属于“按键,执行对应处理函数”流程。
3. 鼠标操控。不同状态下移动鼠标,属于“状态判断、响应鼠标处理函数”流程。
表驱动法