表驱动法应用的难点

好的代码总是将复杂的逻辑以分层的方式降低单个层次上的复杂度。复杂与简单有一个相互转化的过程。

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

时间: 2024-10-03 15:15:55

表驱动法应用的难点的相关文章

善用表驱动法

  最近碰到个需求,计算游戏得分的规则,类似这样: 游戏人数 第一名获得赌注 第二名获得赌注 第三名获得赌注 第四名获得赌注 二人 100% 0% - - 二人(出现2个第1名时) 50% 50%     三人 70% 30% 0% - 三人(出现3个第1名时) 33.3333% 33.3333% 33.3333%   三人(出现2个第1名时) 50%×2 0%     ...... ......     这些奖励规则没有什么规律,随着人数增多,就越发复杂了,并且业务人员可能随时改变这些规则.

深入数据驱动编程之表驱动法的详解_unix linux

数据驱动编程之表驱动法 本文示例代码采用的是c语言.之前介绍过数据驱动编程<浅谈:什么是数据驱动编程的详解>.里面介绍了一个简单的数据驱动手法.今天更进一步,介绍一个稍微复杂,更加实用的一点手法--表驱动法.关于表驱动法,在<unix编程艺术>中有提到,更详细的描述可以看一下<代码大全>,有一章专门进行描述(大概是第八章).简单的表驱动:<浅谈:什么是数据驱动编程的详解>中有一个代码示例.它其实也可以看做是一种表驱动手法,只不过这个表相对比较简单,它在收到消

数据结构教程 第三十二课 哈希表(一)

教学目的: 掌握哈希表的概念作用及意义,哈希表的构造方法 教学重点: 哈希表的构造方法 教学难点: 哈希表的构造方法 授课内容: 一.哈希表的概念及作用 一般的线性表,树中,记录在结构中的相对位置是随机的,即和记录的关键字之间不存在确定的关系,因此,在结构中查找记录时需进行一系列和关键字的比较.这一类查找方法建立在"比较"的基础上,查找的效率依赖于查找过程中所进行的比较次数. 理想的情况是能直接找到需要的记录,因此必须在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键

表驱动方法

1:什么是表驱动法.      表驱动法是一种编程模式(Scheme),从表里面查找信息而不使用逻辑语句(if 和case) 它的好处是消除代码里面到处出现的if.else.swith语句,让凌乱代码变得简明和清晰.对简单情况而言,表驱动方法可能仅仅使逻辑语句更容易和直 白,但随着逻辑的越来越复杂,表驱动法就愈发有吸引力. 2:表驱动法的例子演示     假设有段程序要计算某年某月的天数     通常的做法如下:   1private void btnCalculate_Click(object

等价类结和判定表的软件测试方法应用

摘要:软件测试的类型通常分为白盒测试和黑盒测试,其中基于等价类的划分法与基于判定表的测试法都是较为典型和实用的黑盒测试技术方法.在实际工作中,为了使测试用例的覆盖更加全面,测试目的更加明确,通常不仅仅局限于某一种测试手段.针对等价类和判定表这两种方法各自的特点,可以将两者有机结合,通过对输入条件进行等价类划分,对输出行为进行判定表列举,用综合的手段进行软件测试工作,从而达到使测试用例的设计覆盖全面.条理清晰的目的. 关键词:等价类:判定表:软件测试 1.概述 软件测试的类型一般来说,可以划分为白

架构师速成8.3-可用性之分库分表

有状态分布式,涉及的知识就比较多了,不过我们可以拿几个现实的例子由浅入深的来理解. 数据库的分库分表 假设你是一个开发负责人,开始使用单机的数据库,突然一天数据库硬盘挂掉了.你没有做备份,然后就没有然后了. 进入第2个公司,你意识到备份的重要性,每天定时备份到另一台机器,突然有一天,数据库硬盘挂掉了.你心想幸好我有备份,然后巴拉巴拉的恢复起来,用了2个小时.老板说不错,但是--我们因为宕机造成大量用户流失,信誉下降,然后就又没有然后了.上面说的就是单点的问题. 进入第3个公司,你觉得单点很可怕,

黑盒测试——测试准备阶段

黑盒测试--测试准备阶段 1.概述 1.1 黑盒测试的概念 黑盒测试(black box test)也称功能测试,它是通过测试来检测每个功能是否都能正常使用.在测试中,把程序看作一个不能打开的黑盒子,在完全不考虑程序内部结构和内部特性的情况下,在程序接口进行测试,它只检查程序功能是否按照需求规格说明书的规定正常使用,程序是否能适当地接收输入数据而产生正确的输出信息.黑盒测试着眼于程序外部结构,不考虑内部逻辑结构,主要针对软件界面和软件功能进行测试. 黑盒测试是以用户的角度,从输入数据与输出数据的

数据结构教程 第二十八课 图的存储结构

教学目的: 掌握图的二种存储表示方法 教学重点: 图的数组表示及邻接表表示法 教学难点: 邻接表表示法 授课内容: 一.数组表示法 用两个数组分别存储数据元素(顶点)的信息和数据元素之间的关系(边或弧)的信息. // 图的数组(邻接矩阵)存储表示 #define INFINITY INT_MAX //最大值无穷大 #define MAX_VERTEX_NUM 20 //最大顶点个数 typedef enum{DG,DN,AG,AN} GraphKind;//有向图,有向网,无向图,无向网 typ

用Kettle的一套流程完成对整个数据库迁移

需求: 1.你是否遇到了需要将mysql数据库中的所有表与数据迁移到Oracle. 2.你是否还在使用kettle重复的画着:表输入-表输出.创建表,而烦恼.  下面为你实现了一套通用的数据库迁移流程.  技术引导: 实现之初,在kettle提供的例子中找到了一个类似的(samples\jobs\process all tables). 通过相关改造,终于达到目标.  实现过程解剖: 整套流程分为:2个job,4个trans. 使用到的Trans插件:表输入.字段选择.复制记录到结果.从结果获取