毫无疑问,表驱动是非常重要的一个话题,而在此之前对函数指针与接口抽象的讨论都是为本文表驱动奠定基础。函数指针在表驱动的应用,主要体现在事件的响应函数上,通过示例代码可以清楚地看到;而对于接口抽象中提及的函数接口与消息接口,表驱动都是接口内部最佳的驱动解决方案之一。本文并不打算论述枯燥乏味的理论,因为表驱动本是一种非常实用的编码方法,前面两篇文章已经为其奠定了基础,本文仅针对下述需求通过代码对比呈现表驱动具体应用。
假设程序员面对下述需求:TDM模块是整个产品的一个软件模块,主要完成STM-1/4路由的添加、删除、修改、查询等基本动作,相关的动作命令由用户通过管理维护平台统一下发并转至TDM模块,TDM模块需要根据接收到的命令来完成不同的功能。此外,假设TDM模块的入口函数声明如下:
VOID Tdm_Entry(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
之所以需要假设入口函数声明,是因为上述需求本身并不详尽,程序员需要面对的工作过多,而其中许多并不是本文重点。所谓入口函数,即模块所有功能代码的执行均是从该函数开始的,类似所有程序的执行入口是函数main()。一般情况下,每个模块均有入口函数,其本质上是一种统一的对外接口,以保证代码从此开始顺序执行。但如何调用模块入口函数呢?一般情况下,产品的软件运行是需要操作系统平台以作为支撑的,最常见的如Linux、VxWorks、Android等,而模块入口函数则由操作系统调度管理,当然也可以避开操作系统由上级模块直接调用。需要强调的是,所有程序的最终执行都是顺序的,因为时间的流逝不存在其它可能,而所谓的并行只是概念上的抽象。正是基于此,程序员才可能根据需求分析抽象得到软件模型,最终完成代码开发。
当然,本文并不关心上述需求具体实现,仅针对以上描述完成最基本的代码架构工作,而相关的编码工作主要在入口函数中完成。以下采用if-else、switch-case与表驱动三种形式简单编写了示例代码,并不涉及过多的软件设计开发准则。
1、 if-else驱动
上述问题采用if-else驱动的代码如下所示。
#include <iostream>
using namespace std;
typedef void VOID;
typedef unsigned char BOOL;
typedef unsigned char BYTE;
typedef unsigned short WORD16;
typedef unsigned int WORD32;
typedef float FLOAT;
typedef double DOUBLE;
#define EV_ADD (WORD16)1
#define EV_DELETE (WORD16)2
#define EV_MODIFY (WORD16)3
#define EV_QUERY (WORD16)4
VOID Tdm_Entry(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
WORD16 Tdm_Add(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
WORD16 Tdm_Delete(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
WORD16 Tdm_Modify(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
WORD16 Tdm_Query(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
WORD16 Tdm_Defalut(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
int main(int argc, _TCHAR* argv[])
{
WORD16 wEvent = EV_ADD;
WORD32 wLen = 128;
BYTE *pucIn = new BYTE[wLen];
memset(pucIn, 0x00, sizeof(BYTE)*wLen);
Tdm_Entry(wEvent, pucIn, wLen);
delete [] pucIn;
return 0;
}
VOID Tdm_Entry(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
if (EV_ADD == wEvent)
{
Tdm_Add(wEvent, pucIn, wLen);
}
else if (EV_DELETE == wEvent)
{
Tdm_Delete(wEvent, pucIn, wLen);
}
else if (EV_MODIFY == wEvent)
{
Tdm_Modify(wEvent, pucIn, wLen);
}
else if (EV_QUERY == wEvent)
{
Tdm_Query(wEvent, pucIn, wLen);
}
else
{
Tdm_Defalut(wEvent, pucIn, wLen);
}
}
WORD16 Tdm_Add(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
cout<<"Tdm_Add()"<<endl;
return 0;
}
WORD16 Tdm_Delete(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
cout<<"Tdm_Delete()"<<endl;
return 0;
}
WORD16 Tdm_Modify(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
cout<<"Tdm_Modify()"<<endl;
return 0;
}
WORD16 Tdm_Query(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
cout<<"Tdm_Query()"<<endl;
return 0;
}
WORD16 Tdm_Defalut(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
cout<<"Invalid input!"<<endl;
return 0;
}
2、 switch-case驱动
上述问题采用switch-case驱动的代码如下所示。
#include <iostream>
using namespace std;
typedef void VOID;
typedef unsigned char BOOL;
typedef unsigned char BYTE;
typedef unsigned short WORD16;
typedef unsigned int WORD32;
typedef float FLOAT;
typedef double DOUBLE;
#define EV_ADD (WORD16)1
#define EV_DELETE (WORD16)2
#define EV_MODIFY (WORD16)3
#define EV_QUERY (WORD16)4
VOID Tdm_Entry(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
WORD16 Tdm_Add(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
WORD16 Tdm_Delete(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
WORD16 Tdm_Modify(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
WORD16 Tdm_Query(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
WORD16 Tdm_Defalut(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
int main(int argc, _TCHAR* argv[])
{
WORD16 wEvent = EV_ADD;
WORD32 wLen = 128;
BYTE *pucIn = new BYTE[wLen];
memset(pucIn, 0x00, sizeof(BYTE)*wLen);
Tdm_Entry(wEvent, pucIn, wLen);
delete [] pucIn;
return 0;
}
VOID Tdm_Entry(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
switch (wEvent)
{
case EV_ADD:
Tdm_Add(wEvent, pucIn, wLen);
break;
case EV_DELETE:
Tdm_Delete(wEvent, pucIn, wLen);
break;
case EV_MODIFY:
Tdm_Modify(wEvent, pucIn, wLen);
break;
case EV_QUERY:
Tdm_Query(wEvent, pucIn, wLen);
break;
default:
Tdm_Defalut(wEvent, pucIn, wLen);
break;
}
}
WORD16 Tdm_Add(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
cout<<"Tdm_Add()"<<endl;
return 0;
}
WORD16 Tdm_Delete(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
cout<<"Tdm_Delete()"<<endl;
return 0;
}
WORD16 Tdm_Modify(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
cout<<"Tdm_Modify()"<<endl;
return 0;
}
WORD16 Tdm_Query(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
cout<<"Tdm_Query()"<<endl;
return 0;
}
WORD16 Tdm_Defalut(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
cout<<"Invalid input!"<<endl;
return 0;
}
3、 表驱动
上述问题采用表驱动的代码如下所示。
#include <iostream>
using namespace std;
typedef void VOID;
typedef unsigned char BOOL;
typedef unsigned char BYTE;
typedef unsigned short WORD16;
typedef unsigned int WORD32;
typedef float FLOAT;
typedef double DOUBLE;
#define EV_ADD (WORD16)1
#define EV_DELETE (WORD16)2
#define EV_MODIFY (WORD16)3
#define EV_QUERY (WORD16)4
VOID Tdm_Entry(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
WORD16 Tdm_Add(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
WORD16 Tdm_Delete(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
WORD16 Tdm_Modify(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
WORD16 Tdm_Query(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
WORD16 Tdm_Defalut(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
typedef WORD16 (*PFHANDLE)(WORD16 wEvent, BYTE *pucIn, WORD32 wLen);
typedef struct
{
WORD16 m_wEvent;
PFHANDLE m_pHandle;
}T_EventTable;
T_EventTable g_atMasterEventTable[] =
{
{EV_ADD, Tdm_Add},
{EV_DELETE, Tdm_Delete},
{EV_MODIFY, Tdm_Modify},
{EV_QUERY, Tdm_Query},
};
int main(int argc, _TCHAR* argv[])
{
WORD16 wEvent = EV_ADD;
WORD32 wLen = 128;
BYTE *pucIn = new BYTE[wLen];
memset(pucIn, 0x00, sizeof(BYTE)*wLen);
Tdm_Entry(wEvent, pucIn, wLen);
delete [] pucIn;
return 0;
}
VOID Tdm_Entry(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
WORD16 wEventCnt = sizeof(g_atMasterEventTable) / sizeof(T_EventTable);
PFHANDLE pHandle = Tdm_Defalut;
for (WORD16 wIndex=0; wIndex<wEventCnt; wIndex++)
{
if (wEvent == g_atMasterEventTable[wIndex].m_wEvent)
{
pHandle = g_atMasterEventTable[wIndex].m_pHandle;
}
}
pHandle(wEvent, pucIn, wLen);
}
WORD16 Tdm_Add(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
cout<<"Tdm_Add()"<<endl;
return 0;
}
WORD16 Tdm_Delete(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
cout<<"Tdm_Delete()"<<endl;
return 0;
}
WORD16 Tdm_Modify(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
cout<<"Tdm_Modify()"<<endl;
return 0;
}
WORD16 Tdm_Query(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
cout<<"Tdm_Query()"<<endl;
return 0;
}
WORD16 Tdm_Defalut(WORD16 wEvent, BYTE *pucIn, WORD32 wLen)
{
cout<<"Invalid input!"<<endl;
return 0;
}
通过上述三段代码可以发现,所谓驱动其实就是分支选择语句,其本质上是一种巧妙的编码技巧,不存在过多的深奥理论。
客观地讲,if-else与switch-case有其用武之地。当需要处理的事件规模较小时,表驱动的代价是相对较大的,此时使用if-else或switch-case驱动可以简单明了地解决问题,而使用表驱动则将简单问题复杂化。if-else与switch-case最大的缺陷在于事件规模较大时,驱动分支过多,导致函数的复杂度急剧增加。本质上,if-else与switch-case两种驱动形式是等价的,二者完全可以相互替换。
可能有的程序员对表驱动推崇之至,因为其十分便于后期扩展。例如,当上述TDM模块新增需求后,会有更多的事件需要驱动处理,if-else与switch-case尽管可以实现功能,但随着事件规模的增加代码将逐渐腐败,可维可测性能均大幅降低,而表驱动则可以很高地解决该问题。但是,表驱动的局限也很明显,其要求函数指针类型一致,如果函数指针的接口发生变更,表驱动形式便受到约束。当然,一般情况下,程序员会利用良好的接口抽象避免此类灾难的发生。
本文主要目的是通过对比的形式介绍表驱动,但无论哪种驱动形式,选用时都要结合产品的实际情况。所谓大道至简,软件设计开发中并不能一味追求高明的技巧,而要从整个系统的角度考虑问题。尽管如此,很多产品的软件设计开发中,表驱动仍然是备受推崇的一种驱动形式,毕竟一般产品的软件规模都是较大,动辄几十万行,可维可测面对极大的挑战,而表驱动一定程度上可以缓解此问题。