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