求索

Make Something Different

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

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

阅读更多
文章标签: coding
个人分类: Coding
想对作者说点什么? 我来说一句

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

2016年01月18日 2KB 下载

没有更多推荐了,返回首页

不良信息举报

表驱动法应用的难点

最多只允许输入30个字

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭