【转】 数据驱动编程之表驱动法

http://blog.csdn.net/chgaowei/article/details/6966857

本文示例代码采用的是c语言。
之前介绍过数据驱动编程《什么是数据驱动编程》。里面介绍了一个简单的数据驱动手法。今天更进一步,介绍一个稍微复杂,更加实用的一点手法——表驱动法。
关于表驱动法,在《unix编程艺术》中有提到,更详细的描述可以看一下《代码大全》,有一章专门进行描述(大概是第八章)。

简单的表驱动:
《什么是数据驱动编程》中有一个代码示例。它其实也可以看做是一种表驱动手法,只不过这个表相对比较简单,它在收到消息后,根据消息类型确定使用调用什么函数进行处理。

复杂一点的表驱动:

考虑一个消息(事件)驱动的系统,系统的某一模块需要和其他的几个模块进行通信。它收到消息后,需要根据消息的发送方,消息的类型,自身的状态,进行不同的处理。比较常见的一个做法是用三个级联的switch分支实现通过硬编码来实现:

[cpp] view plaincopy

  1. switch(sendMode)
  2. {
  3. case:
  4. }
  5. switch(msgEvent)
  6. {
  7. case:
  8. }
  9. switch(myStatus)
  10. {
  11. case:
  12. }

这种方法的缺点:
1、可读性不高:找一个消息的处理部分代码需要跳转多层代码。
2、过多的switch分支,这其实也是一种重复代码。他们都有共同的特性,还可以再进一步进行提炼。
3、可扩展性差:如果为程序增加一种新的模块的状态,这可能要改变所有的消息处理的函数,非常的不方便,而且过程容易出错。
4、程序缺少主心骨:缺少一个能够提纲挈领的主干,程序的主干被淹没在大量的代码逻辑之中。

用表驱动法来实现:
根据定义的三个枚举:模块类型,消息类型,自身模块状态,定义一个函数跳转表:

[cpp] view plaincopy

  1. typedef struct  __EVENT_DRIVE
  2. {
  3. MODE_TYPE mod;//消息的发送模块
  4. EVENT_TYPE event;//消息类型
  5. STATUS_TYPE status;//自身状态
  6. EVENT_FUN eventfun;//此状态下的处理函数指针
  7. }EVENT_DRIVE;
  8. EVENT_DRIVE eventdriver[] = //这就是一张表的定义,不一定是数据库中的表。也可以使自己定义的一个结构体数组。
  9. {
  10. {MODE_A, EVENT_a, STATUS_1, fun1}
  11. {MODE_A, EVENT_a, STATUS_2, fun2}
  12. {MODE_A, EVENT_a, STATUS_3, fun3}
  13. {MODE_A, EVENT_b, STATUS_1, fun4}
  14. {MODE_A, EVENT_b, STATUS_2, fun5}
  15. {MODE_B, EVENT_a, STATUS_1, fun6}
  16. {MODE_B, EVENT_a, STATUS_2, fun7}
  17. {MODE_B, EVENT_a, STATUS_3, fun8}
  18. {MODE_B, EVENT_b, STATUS_1, fun9}
  19. {MODE_B, EVENT_b, STATUS_2, fun10}
  20. };
  21. int driversize = sizeof(eventdriver) / sizeof(EVENT_DRIVE)//驱动表的大小
  22. EVENT_FUN GetFunFromDriver(MODE_TYPE mod, EVENT_TYPE event, STATUS_TYPE status)//驱动表查找函数
  23. {
  24. int i = 0;
  25. for (i = 0; i < driversize; i ++)
  26. {
  27. if ((eventdriver[i].mod == mod) && (eventdriver[i].event == event) && (eventdriver[i].status == status))
  28. {
  29. return eventdriver[i].eventfun;
  30. }
  31. }
  32. return NULL;
  33. }

这种方法的好处:
1、提高了程序的可读性。一个消息如何处理,只要看一下驱动表就知道,非常明显。
2、减少了重复代码。这种方法的代码量肯定比第一种少。为什么?因为它把一些重复的东西:switch分支处理进行了抽象,把其中公共的东西——根据三个元素查找处理方法抽象成了一个函数GetFunFromDriver外加一个驱动表。
3、
可扩展性。注意这个函数指针,他的定义其实就是一种契约,类似于java中的接口,c++中的纯虚函数,只有满足这个条件(入参,返回值),才可以作为一
个事件的处理函数。这个有一点插件结构的味道,你可以对这些插件进行方便替换,新增,删除,从而改变程序的行为。而这种改变,对事件处理函数的查找又是隔
离的(也可以叫做隔离了变化)。、
4、程序有一个明显的主干。
5、降低了复杂度。通过把程序逻辑的复杂度转移到人类更容易处理的数据中来,从而达到控制复杂度的目标。

继承与组合
考虑一个事件驱动的模块,这个模块管理很多个用户,每个用户需要处理很多的事件。那么,我们建立的驱动表就不是针对模块了,而是针对用户,应该是用户在某状态下,收到某模块的某事件的处理。我们再假设用户可以分为不同的级别,每个级别对上面的提到的处理又不尽相同。
用面向对象的思路,我们可以考虑设计一个用户的基类,实现相同事件的处理方法;根据级别不同,定义几个不同的子类,继承公共的处理,再分别实现不同的处理。这是最常见的一种思路,可以叫它继承法。
如果用表驱动法怎么实现?直接设计一个用户的类,没有子类,也没有具体的事件的处理方法。它有一个成员,就是一个驱动表,它收到事件后,全部委托给这个驱动表去进行处理。针对用户的级别不同,可以定义多个不同的驱动表来装配不同的对象实例。这个可以叫他组合法。
继承和组合在《设计模式》也有提到。组合的优势在于它的可扩展性,弹性,强调封装性。(继承和组合可以参考这篇文章:面向对象之继承组合浅谈
至于这种情况下的驱动表,可以继续使用结构体,也可以使用对象。

上面的方法的一点性能优化建议:

果对性能要求不高,上面的方法足可以应付。如果性能要求很高,可以进行适当的优化。比如,可以建立一个多维数组,每一维分别表示模块,状态,消息。这样,
就可以根据这三者的枚举直接根据下标定位到处理函数,而不是查表。(其实还是数据驱动的思想:数据结构是静态的算法。)

数据驱动编程再更高级,更为抽象一点的,应该就是流程脚本或者DSL了。我曾经写过一个简单的寄生在xml上的脚本来描述流程。这一块后面抽时间介绍。

时间: 2024-11-12 11:13:19

【转】 数据驱动编程之表驱动法的相关文章

编程学习——表驱动法

近来阅读<代码大全>中“表驱动法”这一章节,发现其编程的思想在C语言实际编程很有指导作用,就想着将“表驱动法”应用于实际项目中. 任务需求:函数在进行业务处理之前,需要对外部输入的数据类型(dataType),数据索引(dataIndex),数据长度(dataLen)进行正确性检查 如果按照if-else结构进行判断的话,代码可能如下所示: enum DATA_TYPE{APPLE=0,PEAR=1,BANANA=2}; enum DATA_LEN{APPLE_LEN=20,PEAR_LEN=

大话设计模式C++版——表驱动法改造简单工厂

上回<大话设计模式C++版--简单工厂模式>中指出了简单工厂模式的缺陷,即违背了开发-封闭原则,其主要原因是由于switch的判断结构的使用,使修改或添加新的对象时需要改动简单工厂类的代码,如何改造switch结构,表驱动法就可以粉墨登场了. 表驱动法的介绍见<数据驱动编程之表驱动法>. 1.面向接口编程,先改造抽象接口类IOperation class IOperation { public: IOperation() : m_nNuml(0), m_nNumr(0) {} vi

编程原则:表驱动法

背景 表驱动法,最早接触这个概念是看<代码大全>,之前也在不自觉的应用,本文对这一个概念再做简短的总结. 表驱动法 说明这个概念之前最好先给出不使用这个概念的代码,常见的需要使用表驱动的场景有如下三种情况: 场景1:不同条件不同数据 if (key = "Key A"){   处理 Key A 相关的数据.}else if (key = "Key B"){   处理 Key B 相关的数据.} 场景2:不同条件不同行为 if (key = "K

[转]什么是数据驱动编程

http://www.cnblogs.com/chgaowei/archive/2011/08/03/2126724.html 前言: 最近在学习<Unix编程 艺术>.以前粗略的翻过,以为是介绍unix工具的.现在认真的看了下,原来是介绍设计原则的.它的核心就是第一章介绍的unix的哲学以及17个设计原 则,而后面的内容就是围绕它来展开的.以前说过,要学习适合自己的资料,而判断是否适合的一个方法就是看你是否能够读得下去.我对这本书有一种相见恨晚的 感觉.推荐有4~6年工作经验的朋友可以读一下

表驱动法 -《代码大全》读书笔记

表驱动法是一种编程模式,从表里面查找信息而不是使用逻辑语句(if-else-switch),当是很简单的情况时,用逻辑语句很简单,但如果逻辑很复杂,再使用逻辑语句就很麻烦了. 比如查找一年中每个月份的天数,如果用表驱动法,完全不需要写一堆if-else-语句,直接把每个月份的天数存到一个数组里就行了,取值的时候直接下标访问,最多针对二月判断一下闰年.这么算的话,平时用的的HashMap,SparseArray也可以算是表驱动 表里可以存数据,也可以存指令,或函数指针等都可以. 示例 看一个例子,

表驱动法 - 巧妙利用数组返回中文星期

Date对象有个getDay方法,它根据本地时间,返回一个具体日期中一周的第几天.返回值从0~6,分别对应周日~周六 getDay 0 1 2 3 4 5 6 星期几 周日 周一 周二 周三 周四 周五 周六 用到日期相关的需求时需要将getDay返回的值转成星期几,即“这一天”是星期几?比如日历组件中选择日历后返回 “2014-12-22 周一”. 这是一段依然在线上运行的代码 /* * 根据Date对象返回星期几 * @param {Date} date * @return {String}

表驱动法

表驱动法:是一种编程模式 将选择条件if else 查表换成直接查表 switch(month){ case  1,3,5 ,7 ,8, 10 ,12:day=30:break: case 2:day=28:break: default:day=30:break: } 换成: day[mouthLen]={31 28 31 30 31 30 31 ,,,} 表驱动法

第18章 表驱动法(1)

这本书讲什么? code complete 是编码完成的意思,是一个软件项目开发过程中的重要里程碑(milestone).软件项目进行到这里,表名已经完成了所有的编码工作,即将开始系统测试.这本书讲的正是为了达到“编码完成”这一重要里程碑所必须的软件构建技术,确切的说,就是如何编写高质量的代码.作者认为,应该首先为人编写代码,其次才是为机器编写.代码主要是供人阅读的.遍布全书的提高代码质量的实实在在的技术和诀窍,是本书最有价值得部分. 表驱动法是一种编程模式(scheme)——从表里面查找信息而

第十八章 表驱动法

表驱动法是一种编程模式--从表里面查找信息而不使用逻辑语句(if和case). 表驱动法使用总则 在适当的环境下,采用表驱动法,所生成的代码会比复杂的逻辑代码更简单.更容易修改,而且效率更高. 使用表驱动法的两个问题 使用表驱动法必须先解决两个问题.首先,你必须要回答怎样从表中查询条目的问题.其次是考虑在表中存些什么. 直接访问表 和所有的查询表一样,直接访问表代替了更为复杂的逻辑控制结构.之所以说它们是"直接访问"的,是因为你 无须绕很多复杂的圈子就能够在表里面找到你想要的信息. 索