使用表驱动技术优化程序结构
2011-05-25 09:40
453 查看
1.前言
所谓的表驱动技术实际上是一种回调函数(callback function),它能够使调用者在完全不知道细节的情况下完成复杂的操作。使用这种技术不但可以使你的代码更加紧凑,还能够降低各个模块的耦合程度,优化程序结构。表驱动的适用范围很广,无论是底层驱动开发,还是上层通讯都可以用到,甚至Microsoft著名的MFC中的消息映射也使用了这一技术。本文结合一个例子介绍了如何在实际工程中使用该技术。
2.简化你的switch-case
试想一下,如果你的程序需要处理这么一种情况:你会不停的接收到各种各样的外部请求,然后需要在自己的函数中针对不同请求作出相应的反应,你该怎么做?最直接的想法无非是使用分支语句来调用不同的函数,比如下面的代码:
BOOL DispatchFunction(COMMANDMSG CommandID) { switch(CommandID) { case COMMAND1: { Func1(); break; } case COMMAND2: { Func2(); break; } …… default: { return FALSE; } } return TRUE; } |
如果你是个有一定经验的程序员,你可能会考虑到使用宏来将每个case分支简化成一行。的确,这也是不错的方法,但是治标不治本。能不能找到一种方法将一个上百行的函数简化到区区不到十行呢?
答案是肯定的。我们可以构建一个数组,数组中每个元素存放着外部请求和对应操作的映射关系。现在,问题就被简化成在一个循环当中遍历一个数组,抽取请求,然后执行操作。像刚才的程序就可以这么写:
BOOL DispatchFunction(COMMANDMSG CommandID) { for (int i = 0; i < nItemCount; i++) { if (DispatchTable[i].KeyVal == CommandID) { *DispatchTable[i].HandleFunc(); return TRUE; } } return FALSE } |
在上面的程序中,DispatchTable是一个结构体数组,也就是我们这个函数的驱动表。KeyVal是键值,HandleFunc是指向函数的指针,它们被包含在一个结构体中,并存放在驱动表里。函数通过访问表中的每个元素与外部请求CommandID进行比对,如果一致,则通过函数指针来调用相应的函数。这样,我们所作的工作就与刚才那个上百行的分支函数是一样的了。
另一方面,你可能还注意到这种写法会带来额外的好处。如果增加了新的请求方式,或者必须改写旧有的请求关系,我们根本不需要修改函数DispatchFunction,只要维护这个驱动表DispatchTable就可以完成这个工作了。因为这个函数甚至不清楚它自己干了些什么。它就像一个盒子,将驱动表装载到里面,然后执行一些简单的操作。你甚至能够在运行时替换掉里面的驱动表,从而达到动态装载的目的。
接下来我们来看一个例子,这种技术可以帮助我们完成什么样的任务。
3.例子:使用表驱动编写你的命令解释器
在通讯程序中,命令解释器是一个必不可少的组件。我们将底层收上来的报文加以拆分,提取中间的命令字段交给命令解释器来解释,之后执行各种操作。由此可见,命令解释器本身就是一个具有多分支的程序,正好适合采用表驱动。这里举的例子是在笔者参与的实际工程中使用的命令解释器。首先必须定义函数指针和驱动表中元素的结构体,代码如下:
typedef void(CCmdTarget::*HANDLEFUNC)(PBYTE lpBuffer); typedef struct _DISPATCHITEM { BYTE m_byKeyVal; HANDLEFUNC m_HandleFunc; }DISPATCHITEM, *PDISPATCHITEM; |
命令解释器的代码如下:
BOOL CSCIComm::CommandInterpreter(BYTE byCommandID, PBYTE lpBuffer) { for (int i = 0; ; i++) { if (m_DispatchTbl[i].m_byKeyVal == COMMPTL_RESERVED_DISPATCHTBLEND) { return FALSE; } else if (m_DispatchTbl[i].m_byKeyVal == byCommandID && m_DispatchTbl[i].m_HandleFunc) { (m_pPortOwner->*m_DispatchTbl[i].m_HandleFunc)(lpBuffer); return TRUE; } } } |
通过使用表驱动来编写命令解释器,可以得到很多好处:
首先是代码的简洁性,这个优点不言自明。
其次,你可以将底层代码和协议层分离。命令解释器属于下层,它会反上来很多命令,然后加以派送,但是它根本不需要关心该如何处理这些问题,因为只要加载驱动表然后“按图索骥”就好了。
最后,你甚至可以在程序运行中动态装载驱动表,从而达到不停止运行就能改变通信协议的目的,这一点提供了极高的灵活性。
你看,一个基本的表驱动结构优点多多而且实现起来也很简单。我们需要的额外操作只是构建这个驱动表并且对其加以维护而已。接下来,我会另外介绍一些小技巧来使这部分程序获得更大的方便性和灵活性,尽管这部分内容并不直接属于表驱动的范畴。
4.还可以更加灵活
前面提到,表驱动本身需要驱动表的构建。关于构建驱动表,你可以在类的构造函数中或者某个窗体的OnCreate或是OnInitalUpdate中,构建一个静态的数组,然后顺带进行初始化赋值。但是,如果你考虑到代码的可读性,以及你的代码和其他代码的交互性。比如解释层和底层是由你来做,但是协议层是别人来做。你就不得不考虑如何能让别人更方便的构建想要的驱动表。这里提供一些小技巧可以作为参考。
首先,你可以定义一些宏,采用宏的方式将命令段和执行函数映射到这张表里。当然它们会标识有起始和结束的标志。在例子中定义如下:
#define DISPATCH_MAP_BEGIN(thePointer) / if (!*thePointer){ / static const DISPATCHITEM _DispatchTbl[] = / { #define DISPATCH_ITEM_MAP(byKeyVal, pHandleFunc) / { (BYTE)byKeyVal, (HANDLEFUNC)pHandleFunc }, #define DISPATCH_MAP_END(thePointer) / { COMMPTL_RESERVED_DISPATCHTBLEND, NULL } / }; / *thePointer = (PDISPATCHITEM)&_DispatchTbl[0]; / } |
而DISPATCH_ITEM_MAP就是负责命令字和执行操作的映射,它会把二者写入建立好的静态数组中。
具体的使用如下所示:
PDISPATCHITEM* pointer = m_SCIComm.GetDispatchTbl(); DISPATCH_MAP_BEGIN(pointer) DISPATCH_ITEM_MAP(COMMPTL_REQ_INPUT, &CMainFrame::TrackParamHandler) DISPATCH_ITEM_MAP(COMMPTL_REQ_OUTPUT, &CMainFrame::TrackParamHandler) DISPATCH_ITEM_MAP(COMMPTL_MDY_INPUT, &CMainFrame::ModifyInputHandler) DISPATCH_ITEM_MAP(COMMPTL_MDY_INPUT_E2, &CMainFrame::ModifyInputHandler) DISPATCH_ITEM_MAP(COMMPTL_IDLECYC_MAX, &CMainFrame::SaveIdleCycleMaxHandler) …… DISPATCH_MAP_END(pointer) |
另外,介绍的例子中命令解释器接收的参数byCommandID使用的是BYTE类型。为了提高可扩展性,你完全可以向其中传入一个结构体指针,这样就可以容纳更多的信息。但是,相对的,关于比较操作也必须传入一个回调函数,利用其进行比对操作。对于执行函数也是同样的道理。由于执行函数进行的操作千差万别,参数和返回值很有可能不一致。我们可以用一个结构体指针来替代固定的类型,这样就具有更大的灵活性了。
5.总结
表驱动技术是一种可以使你的代码更简洁,结构更加灵活的技术,最适用于多分支的函数当中。另外,我们可以配合驱动表编写一些可以灵活配置的宏,这样能让你的程序修改起来更加得心应手。相关文章推荐
- EA&UML日拱一卒-微信小程序实战:位置闹铃 (13)-使用类优化程序结构
- 使用单文档视图结构把Word嵌入到VC程序中(2)
- android 技术总结 图片优化 sqlite使用 Fragment使用
- --=== 让你的程序开始说话(在VB中使用文字朗读引擎(TTS)技术)===--
- 使用Xdebug调试和优化PHP程序[1]
- (译)如何优化cocos2d程序的内存使用和程序大小:第一部分
- (译)如何优化cocos2d程序的内存使用和程序大小:第一部分
- 使用Xdebug调试和优化PHP程序[1]
- 【技术原创】探讨一下京东商城价格图片解析算法的优化,附演示程序下载
- 第二十二讲 使用缓存优化程序性能
- 使用OSCache优化程序性能
- Memcache技术分享:介绍、使用、存储、算法、优化、命中率
- 转:使用XHProf优化PHP程序
- 如何优化cocos2d/x程序的内存使用和程序大小
- 关于linux驱动(应用)程序头文件使用
- 使用设计模式改善程序结构(三)
- 如何优化cocos2d/x程序的内存使用和程序大小
- 使用数据库连接池优化程序性能
- overlayfs存储驱动的使用以及技术探究
- 使用系统配置程序来优化系统启动项目提高启动速度