传统的以多态取代switch...case
Martin Flower的著作《重构》告诉我们应该以多态取代switch...case,比如一个经典的Shape绘图的例子:
void Draw( Shape* shape ) {
switch( shape.type ) {
case line: dynamic_cast< Line* >( shape )->Draw(); break;
case rect: dynamic_cast< Rect* >( shape )->Draw(); break;
}
}
使用多态则变为:
void Draw( Shape* shape ) {
shape->Draw();
}
注意前者的Draw为非虚函数,后者则为虚函数。
多态无法实施
可是并非switch...case总是处理多态对象,比如面对一个消息ID。
此时switch...case也并非如想象的那么遭糕,至少在其case条目比较少的情况下能够工作的很好。
比如下面这段处理消息的代码:
void FunctionA( Message* message );
...
void FunctionN( Message* message );
void HandleMessage( Message* message ) {
switch ( message->GetID() ) {
case MessageID_A:
FunctionA( message );
break;
...
case MessageID_N:
FunctionN( message );
break;
default:
assert( false && "Unknown message" );
}
}
switch...case效率低下
可是当消息变得越来越多时,switch...case的效率将成为问题,因为它执行的是个线性查找过程。
比如我们要查找的case条目正是最好一条,switch...case则会将比较前面的每一条case,此过程跟if..else if..else类似。
曙光:使用查找表
要提高查找速度,一个方法是使用查找表(你可以使用map查找或者对排序的vector做二分查找)。
第一步要做的就是做出一个Lookup函数,给一个ID返回一个值(函数指针或者其他),以封装switch...case过程。
可能的实现如下:
typedef void (*MsgFunc)( Message* message );
MsgFunc Lookup( Message* message ) {
typedef map<int, MsgFunc> Registry;
struct RegistryInitializer {
const Registry& operator()() {
static Registry registry;
registry[MessageID_A] = &FunctionA;
...
registry[MessageID_N] = &FunctionN;
return registry;
}
};
// 使用局部static实现惰性初始化
static const Registry& registry = RegistryInitializer()();
Registry::const_iterator it = registry.find( i );
if ( it != registry.end() ) {
return it->second;
}
else {
assert( false && "Unknown message id" );
return NULL;
}
}
然后HandleMessage使用Lookup搜寻出处理函数并调用之。
void HandleMessage( Message* message ) {
MsgFunc msgFunc = Lookup( message->GetID() );
if ( msgFunc != NULL ) {
(*msgFunc)();
}
else {
assert( false && "Unknown message" );
}
}
可以看出使用查找表不但效率高,还可以将映射集中初始化,每次有新的条目增加进来,只需要简单的加一句
registry[MessageID_X] = &FunctionX;
至少比switch...case增加条目是少打几个字。
结论
在条目较少的情况时使用switch...case依然是个不错的选择,因为作出一个查找表并非是个轻松的工作;如果条目很多,那么用查找表吧!
Martin Flower的著作《重构》告诉我们应该以多态取代switch...case,比如一个经典的Shape绘图的例子:
void Draw( Shape* shape ) {
switch( shape.type ) {
case line: dynamic_cast< Line* >( shape )->Draw(); break;
case rect: dynamic_cast< Rect* >( shape )->Draw(); break;
}
}
使用多态则变为:
void Draw( Shape* shape ) {
shape->Draw();
}
注意前者的Draw为非虚函数,后者则为虚函数。
多态无法实施
可是并非switch...case总是处理多态对象,比如面对一个消息ID。
此时switch...case也并非如想象的那么遭糕,至少在其case条目比较少的情况下能够工作的很好。
比如下面这段处理消息的代码:
void FunctionA( Message* message );
...
void FunctionN( Message* message );
void HandleMessage( Message* message ) {
switch ( message->GetID() ) {
case MessageID_A:
FunctionA( message );
break;
...
case MessageID_N:
FunctionN( message );
break;
default:
assert( false && "Unknown message" );
}
}
switch...case效率低下
可是当消息变得越来越多时,switch...case的效率将成为问题,因为它执行的是个线性查找过程。
比如我们要查找的case条目正是最好一条,switch...case则会将比较前面的每一条case,此过程跟if..else if..else类似。
曙光:使用查找表
要提高查找速度,一个方法是使用查找表(你可以使用map查找或者对排序的vector做二分查找)。
第一步要做的就是做出一个Lookup函数,给一个ID返回一个值(函数指针或者其他),以封装switch...case过程。
可能的实现如下:
typedef void (*MsgFunc)( Message* message );
MsgFunc Lookup( Message* message ) {
typedef map<int, MsgFunc> Registry;
struct RegistryInitializer {
const Registry& operator()() {
static Registry registry;
registry[MessageID_A] = &FunctionA;
...
registry[MessageID_N] = &FunctionN;
return registry;
}
};
// 使用局部static实现惰性初始化
static const Registry& registry = RegistryInitializer()();
Registry::const_iterator it = registry.find( i );
if ( it != registry.end() ) {
return it->second;
}
else {
assert( false && "Unknown message id" );
return NULL;
}
}
然后HandleMessage使用Lookup搜寻出处理函数并调用之。
void HandleMessage( Message* message ) {
MsgFunc msgFunc = Lookup( message->GetID() );
if ( msgFunc != NULL ) {
(*msgFunc)();
}
else {
assert( false && "Unknown message" );
}
}
可以看出使用查找表不但效率高,还可以将映射集中初始化,每次有新的条目增加进来,只需要简单的加一句
registry[MessageID_X] = &FunctionX;
至少比switch...case增加条目是少打几个字。
结论
在条目较少的情况时使用switch...case依然是个不错的选择,因为作出一个查找表并非是个轻松的工作;如果条目很多,那么用查找表吧!