表驱动法
1、相信很多刚从事工作的程序员或一些初级程序员在写代码的时候会出现对一些逻辑判断写成多层if-else嵌套的经历,这种方式在一些简单的层次中运用起来确实可行,但对于一些大型项目逻辑判断比较多的情况下,一下子嵌套十几个甚至更多的if-else就会显得吃力、混乱,降低了可读性。因此今天我要介绍一种代替if-else的方式-----表驱动法。
2、相对比于switch-case这种方式,表驱动法会更有效些;使用表驱动可以提高源程序的可读性,使之更简洁而且更容易修改与扩充。
3、对于表驱动方式根据其复杂度可以分为三种:(1)直接访问(2)索引访问(3)阶梯访问(分段查找)
4、使用表驱动时候的两个问题:
(1)
(2)
5、表驱动的另外一个优势就是:你可以把表里里面的数据存放在一个单独的文件中,当程序运行到这里的时候会自动去读取文件中的内容,我们就可以在数据改变的情况下不改变源程序,而只改变文件中的数据即可,这样也可以实现数据的便捷扩充,使得数据与程序分离开来,降低耦合度。
6、
以上图片内容摘录自《代码大全》一书18章。
7、表驱动应用实例:(类比中断向量表,函数指针指向函数实现)
代码示例(1):
考虑一个消息(事件)驱动的系统,系统的某一模块需要和其他的几个模块进行通信。它收到消息后,需要根据消息的发送方,消息的类型,自身的状态,进行不同的处理。比较常见的一个做法是用三个级联的switch分支实现通过硬编码来实现:
- switch(sendMode)
- {
- case:
- }
- switch(msgEvent)
- {
- case:
- }
- switch(myStatus)
- {
- case:
- }
这种方法的缺点:
1、可读性不高:找一个消息的处理部分代码需要跳转多层代码。
2、过多的switch分支,这其实也是一种重复代码。他们都有共同的特性,还可以再进一步进行提炼。
3、可扩展性差:如果为程序增加一种新的模块的状态,这可能要改变所有的消息处理的函数,非常的不方便,而且过程容易出错。
4、程序缺少主心骨:缺少一个能够提纲挈领的主干,程序的主干被淹没在大量的代码逻辑之中。
用表驱动法来实现:
根据定义的三个枚举:模块类型,消息类型,自身模块状态,定义一个函数跳转表:
typedef struct __EVENT_DRIVE
- {
- MODE_TYPE mod;//消息的发送模块
- EVENT_TYPE event;//消息类型
- STATUS_TYPE status;//自身状态
- EVENT_FUN eventfun;//此状态下的处理函数指针
- }EVENT_DRIVE;
- EVENT_DRIVE eventdriver[] = //这就是一张表的定义,不一定是数据库中的表。也可以使自己定义的一个结构体数组。
- {
- {MODE_A, EVENT_a, STATUS_1, fun1}
- {MODE_A, EVENT_a, STATUS_2, fun2}
- {MODE_A, EVENT_a, STATUS_3, fun3}
- {MODE_A, EVENT_b, STATUS_1, fun4}
- {MODE_A, EVENT_b, STATUS_2, fun5}
- {MODE_B, EVENT_a, STATUS_1, fun6}
- {MODE_B, EVENT_a, STATUS_2, fun7}
- {MODE_B, EVENT_a, STATUS_3, fun8}
- {MODE_B, EVENT_b, STATUS_1, fun9}
- {MODE_B, EVENT_b, STATUS_2, fun10}
- };
- int driversize = sizeof(eventdriver) / sizeof(EVENT_DRIVE)//驱动表的大小
- EVENT_FUN GetFunFromDriver(MODE_TYPE mod, EVENT_TYPE event, STATUS_TYPE status)//驱动表查找函数
- {
- int i = 0;
- for (i = 0; i < driversize; i ++)
- {
- if ((eventdriver[i].mod == mod) && (eventdriver[i].event == event) && (eventdriver[i].status == status))
- {
- return eventdriver[i].eventfun;
- }
- }
- return NULL;
- }
这种方法的好处:
1、提高了程序的可读性。一个消息如何处理,只要看一下驱动表就知道,非常明显。
2、减少了重复代码。这种方法的代码量肯定比第一种少。为什么?因为它把一些重复的东西:switch分支处理进行了抽象,把其中公共的东西——根据三个元素查找处理方法抽象成了一个函数GetFunFromDriver外加一个驱动表。
3、可扩展性。注意这个函数指针,他的定义其实就是一种契约,类似于java中的接口,c++中的纯虚函数,只有满足这个条件(入参,返回值),才可以作为一个事件的处理函数。这个有一点插件结构的味道,你可以对这些插件进行方便替换,新增,删除,从而改变程序的行为。而这种改变,对事件处理函数的查找又是隔离的(也可以叫做隔离了变化)。、
4、程序有一个明显的主干。
5、降低了复杂度。通过把程序逻辑的复杂度转移到人类更容易处理的数据中来,从而达到控制复杂度的目标
代码示例(2):
博客 :http://www.cnblogs.com/clover-toeic/p/3730362.html
--------------2017.7.9