现在的位置: 首页 > 综合 > 正文

表驱动法应用的难点

2017年09月28日 ⁄ 综合 ⁄ 共 4629字 ⁄ 字号 评论关闭

分类:
Coding
550人阅读评论(0)收藏举报
好的代码总是将复杂的逻辑以分层的方式降低单个层次上的复杂度。复杂与简单有一个相互转化的过程。

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){

returnfalse;

}
vPointer->setValue(value);

returntrue;

}

template<typename T> T getValue(PROPERTY_IDid)

{

int index =getRowOfProperty(id);
if(index==-1 )

returnNULL;
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

抱歉!评论已关闭.