表驱动法应用的难点

原创 2013年07月31日 23:21:03
好的代码总是将复杂的逻辑以分层的方式降低单个层次上的复杂度。复杂与简单有一个相互转化的过程。

1. 表驱动法

在涉及编码解析的功能时,常常有一个带有长长一串case的switch,而且会不断增长。为每一个case搞个类就太夸张了,还是用表驱动(Table Driven)来取代它吧。这种应用已经太多了。而让大家不去用的原因可能就一个,认为表无法表达出属性的差异。

比如一般而言有下面这样一个操作,大家肯定会觉得用表处理没有问题。
   Operator         Function
   add(x,y)           addFunctionPtr
   sub(x,y)           subFunctionPtr

但是如果增加了一个inc(x),它相对于先前两个各有两个参数就不一样了。这就会被认为提取共性失败,而放弃了表驱动法,又栽到switch..case的汪洋大海。

表驱动法就是两个过程: i. 提取共性,做到为每个元素做一样的事。把共性放到表中, 用查表法取代switch。 ii. 提供支持函数,因为提取共性后会要求有为这些共性服务的函数。第1步是比较简单的,把第2步想透了才会提升使用表驱动法的层次。

表驱动法,一方面简化代码实现,便于维护。(这里的简化强调地是上层实现逻辑的简化。) 二是提高代码的弹性,增减内容的成本低,甚至可以做到动态支持。

用一个简单的定义表驱动法的使用:
const TABLE table[] = 
{
    { ID, TARGET },
};

void handlingFunction(ID)
{
    for(iterator row =table.begin(); row!=table.end();row++)
     {
          if (ID == row.id)
          {
               callHandleFunctionForTheRow( row );
          }
     }
}
 对应于上面两个要点,就是TABLE的定义和callHandleFunctionForTheRow的写法。 callHandleFunctionsForRow为每一行的处理提供了一个一致的入口,这是表驱动法实现的关键,其原则为对每个元素都做同样的事情,这里同样的事情应至少要理解为接口一致!下面讨论两个在实践中常见的问题:兼容不同数据类型和兼容不同参数个数。

像前面的例子,只要让Function的定义兼容两个参数和一个参数的情况就可以了。使用模板是一个最佳解决方法,就是比较复杂一些。这个解决方案最后稍带说一下。

2.问题说明及初步实现

先举个例子说明一下公共函数的实现思路。 一个维护一串属性的类有三个方法,一个写值,一个取值,还有一个序列化函数(存储和加载)。可以想像,如果加一个属性,就要增加至少三处代码。(下面的示例没有涉及序列化。)

下面是一个向特别Key写入其值的操作,Key代表的数据类型可能会不一样, 如下示例(只用说明要点):
String getKeyValue(String&key)
{
   switch(key)
   {
     case ID1: //这是个整型数据
       value =  String::number(d->id1);
       break;

     case ID2: //这个是字串
        value = String::copy(d->id2);
        break; 
     …….
    }
    return value;
}


使用表驱动法,最简单的一种方法就是用函数重载来处理类型不同的情况。可以使用类似如下的方法实现:
//表的定义
static PropertyTableValue propertyTable[] = {
    { ID1, TYPE_INT, 0},
    { ID2, TYPE_STRING, (intptr_t)(void*)malloc(100)},
    { ID_NONE, TYPE_INT NULL}
};

//写值,重载两个函数来实现
bool Settings::setValue(PROPERTY_ID id, constchar *value)
{
    constvoid *pValue = reinterpret_cast<constvoid*>(value);
   
    returnsetValue(id,pValue);
}

bool Settings::setValue(PROPERTY_ID id, int value)
{
    int index = getRowOfProperty(id);
   
    if(index==-1 || propertyTable[index].type!=TYPE_INT)
        returnfalse;
   
    propertyTable[index].value = (intptr_t)value;
   
    returntrue;
}


//取值
int Settings::getIntValue(PROPERTY_ID id)
{
    int index = getRowOfProperty(id);
   
    if(index==-1 || propertyTable[index].type!=TYPE_INT)
        return0xffffffff;
   
    return (int)(propertyTable[index].value);
}
const char* Settings::getStringValue(PROPERTY_ID id)
{
    int index = getRowOfProperty(id);
   
    if(index==-1 || propertyTable[index].type!=TYPE_STRING)
        returnNULL;
   
    return reinterpret_cast<constchar *>(propertyTable[index].value);
}

3. 继续优化

这样的代码,只是勉强实现了表驱动的功能。观察代码可以看到仍然有两个明显的问题:
  1. 表的定义太死板,缺少灵活性。在定义表时分配内存问题明显。
  2. setValue和getValue仍然有不少重复的代码逻辑。

针对第一个问题,可以使用智能指针和两个表维护的接口来解决,实现动态增减属性。针对第二个问题,是典型的C++多态问题,可以使用模板类来实现。即实现一个代表值的类,将取值操作交给这个类完成。

如:
template<typename T>
class SettingValue{
    ~SettingValue(){};
public:
    T getValue() {return value;};
    bool setValue(T newValue) { value = newValue; return true;}
private:
    T value;
};

对于字串的类,还需要特化处理,包括内存回收和赋值操作。下面给出一个赋值操作的特化实现:
template <>
bool SettingValue<char *>::setValue(char * newValue)
{
    if(value)
    {
        delete value;
    }
   
    strcpy(value,newValue);
   
    return true;
}

在这个基础上,要实现动态表就更容易了。下面实现出另外一份Setting Manager:
struct PropertyTableValueV2 {
    const PROPERTY_ID key; // property id
    const PROPERTY_TYPE type; //value type
    void *value;
};

static PropertyTableValueV2 propertyTable[] = {
    { ID1, TYPE_INT, newSettingValue<int>},
    { ID2, TYPE_STRING, newSettingValue<constchar *>},
    { ID_NONE, TYPE_INT NULL}
};

class SettingsV2 {
public:
    template<typename T> bool setValue(PROPERTY_IDid, T value)
     {       
SettingValue<T> *vPointer = reinterpret_cast<SettingValue<T> *>(getSettingValueOfProperty(id));        if(!vPointer){            
return false;        
}
        vPointer->setValue(value);        
return true;     
}
   
    template<typename T> T getValue(PROPERTY_IDid)    
{        
int index = getRowOfProperty(id);
        if(index==-1 )            
return NULL;
        SettingValue<T> *vPointer = reinterpret_cast<SettingValue<T> *>(getSettingValueOfProperty(id));
        return vPointer->getValue();     } 
};
///////[Horky]省略部分代码///////
};

*注意在使用getValue时要在函数后面加类型约束,如下:
    printf("SettingsV2: id1 is %d\n", (mySetting.getValue<int>(ID1)));
    printf("SettingsV2: id2 is %s\n",(mySetting.getValue<char *>(ID2)));


4.进一步优化

到这里,实现了一个比较正常的表驱动方法了。但就问题而言,还至少有两个化点:
  a. 实现动态表。进一步降低属性变化的成本,也更符合封装的原则。
  b. 将枚举ID改为字串属性,即以字串为id (以hash方式处理,不会明显增加运行成本), 更加灵活。特别有利于序列化可读的文本。

如果要再进一步,还可以增加一个KEY的类型,来实现出可嵌套的参数设置。比如:
  id2: (int)0
  id2: (string)xhorkyxxx
  id3: (key)
     id3_1:(int)1
     id3_2:(float)0.3

5. 关于兼容多参数

针对开篇提到的问题,其实现方式也是使用模板类实现,贴出支持两个参数的类定义就能理解了(如果只是针对这个例子,就有点杀机焉用宰牛刀的感觉!WebKit的代码里这类似的应用。):
template<typename P1,typename P2>
class OperatorWithParameter2 {
public:
    typedef void (*Method)(P1,P2);
    typedef const P1& Param1;
    typedef const P2& Param2;
   
    static OperatorWithParameter2* create(Method method, Param1 parameter1,Param1 parameter2)
    {
        return (new OperatorWithParameter2(method, parameter1,parameter2));
    }
   
private:
    OperatorWithParameter2(Method method, Param1 parameter1, Param2 parameter2)
    : m_method(method)
    , m_parameter1(parameter1)
    , m_parameter2(parameter2)
    {
    }
   
    virtualvoid perform()
    {
        (*m_method)(m_parameter1,m_parameter2);
    }
   
private:
    Method m_method;
    P1 m_parameter1;
    P2 m_parameter2;
};

转载请注明出处:http://blog.csdn.net/horkychen

java enum多路分发下浅识表驱动式编程

由于java的动态绑定只能处理一个类型,所以如果要执行的操作包含不止一个类型未知的对象时,就需要使用多路分发.其中多路分发使用了多态机制,所以只能发生在方法调用时.以石头,剪刀,布的例子说明  ...
  • likaiwalkman
  • likaiwalkman
  • 2016年05月03日 16:58
  • 402

基于表驱动法的三种实现方法

  • 2016年01月18日 22:44
  • 2KB
  • 下载

数据驱动编程与表驱动法(多if-else结构精简)

转载链接地址 http://tec.5lulu.com/detail/108asn4wm11y68sdc.html 1数据驱动编程的核心 数据驱动编程的核心出发点是相对于...
  • inuyashaw
  • inuyashaw
  • 2016年12月01日 16:41
  • 1007

测试驱动开发在PHP中的应用

本文已被成都计算所《计算机应用》采用,版权属于该杂志与作者共同所有,并不同于其他在网上发表之文章,请勿擅自转载。 1.   TDD 测试驱动开发(Test-Driven Development, TD...
  • oxware
  • oxware
  • 2004年07月29日 21:59
  • 3866

PHP资源列表

PHP资源列表 一个PHP资源列表,内容包括:库、框架、模板、安全、代码分析、日志、第三方库、配置工具、Web 工具、书籍、电子书、经典博文等等。 初始翻译信息来自:《推荐!国外程序员整理的 ...
  • u013279509
  • u013279509
  • 2017年11月29日 14:28
  • 144

表驱动法应用的难点

表驱动法应用的难点 分类: Coding2013-07-31 23:21 519人阅读 评论(0) 收藏 举报 coding 目录(?)[+] 好的代码...
  • pi9nc
  • pi9nc
  • 2014年03月21日 15:32
  • 722

《代码大全》之表驱动法

表驱动法是一种编程模式——从表里查找信息而不是使用逻辑语句(if 和 case)。事实上,凡是能通过逻辑语句来选择的事务,都可以通过查表来选择。对简单的情况而言,使用逻辑语句更为容易和直白。但随着逻辑...
  • huihuibisheng
  • huihuibisheng
  • 2015年12月10日 19:40
  • 811

把 if-else 的代码风格改成表格驱动法的意义

php中表格驱动写法及代码重构,用来取代if else写法,且宜于维护。
  • hb01846
  • hb01846
  • 2017年10月09日 17:00
  • 94

PHP5_官方中文手册

  • 2013年04月08日 14:57
  • 29.4MB
  • 下载

表驱动法----代码大全2,第18章学习笔记

0. 一个原则,多种好处      编写到表里的数据,比嵌入到代码中的数据更易于维护。表驱动法的核心思想,就是帮助编程人员将融入到程序结构中的那些数据提取出来。这里的“那些数据”是指逻辑判断数据。这样...
  • qianxiangyu
  • qianxiangyu
  • 2011年04月10日 20:11
  • 660
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:表驱动法应用的难点
举报原因:
原因补充:

(最多只允许输入30个字)