一个用C实现的Dispatch框架
用条件语句做分发是一件很常见的事:
switch(msg->id) {
case ID1:
ID1Handler(msg);
break;
case ID2:
ID2Handler(msg);
break;
...
}
条件稍微多一些点,函数就会变得冗长不堪。有的团队会直接用if..else,进行判断,于是我们有幸知道了,VC不支持超过128个的选择分支。为了让这种代码的可维护性更好,我们做了一些尝试。
下面定义了一个dispatcher:
BEGIN_DISPATCHER(MSG, ID, MsgHandler)
DISPATCH_ITEM(ID1, ID1Handler)
DISPATCH_ITEM(ID2, ID2Handler)
END_DISPATCHER(PCU_DisasterHandler)
首先,用BEGIN_DISPATCH_MAP定义了这个dispatcher的名字(MSG),用做分发键值的类型(ID)和处理函数的类型(MsgHandler)。接下来,用DISPATCH_ITEM定义了几个分发项,也就是说,如果传入的值是ID1,会用ID1Handler进行处理,如果是ID2,则对应着ID2Handler。最后,用END_DISPATCH_MAP定义了一个错误处理函数。这样的话,就把使用的时候,就不必额外去做判空的操作了。这是Null Object模式的一种体现。
这个dispatcher的使用方式如下:
dispatch(to(MSG), with(msg->id))(msg);
这段代码的含义是使用MSG这个dispatcher,根据msg->id找到对应的处理函数,传入的参数是msg。
这个dispatch框架的实现如下:
#include <string.h>
#define SIZE_OF_ARRAY(array) sizeof(array)/sizeof(array[0])
/* dispatcher definition */
#define __DISPATCHER_NAME(name) __dispatcher_##name
#define __DISPATCH_ITEM_NAME(name) __dispatch_item_##name
#define __IsMatched(target, source) (0 == memcmp(&target, &source, sizeof(target)))
#define BEGIN_DISPATCHER(name, key_type, handler_type) \
struct __DISPATCH_ITEM_NAME(name) {\
key_type key;\
handler_type *handler;\
};\
\
handler_type* __DISPATCHER_NAME(name)(key_type key) \
{\
static struct __DISPATCH_ITEM_NAME(name) dispatchers[] = {\
#define END_DISPATCHER(disaster_handler) \
};\
int i;\
int array_size = SIZE_OF_ARRAY(dispatchers); \
for (i = 0; i < array_size; i++)\
{\
if (__IsMatched(dispatchers[i].key, key)) {\
return dispatchers[i].handler;\
}\
}\
return disaster_handler;\
}
#define DISPATCH_ITEM(key, handler) {key, handler},
#define DISPATCH_ITEM_2(key1, key2, handler) {key1, key2, handler},
#define DISPATCH_ITEM_3(key1, key2, key3, handler) {key1, key2, key3, handler},
#define dispatch(name, key) __DISPATCHER_NAME(name)(key)
#define to(name) name
#define with(key) key
从这里可以看出,定义dispatcher,实际上是定义了一个函数,这个函数的返回值是一个函数指针,而这个函数指针的类型是由handler_type定义的。这样的话,就解决了不同dispatcher之间函数参数不同的问题。
当然,这个处理里面采用了最简单的数组,在分发项不是很多的情况下是适用的。如果分发项较多,可以考虑改为map实现。另外,这里还运用了一些宏的技巧。在写应用代码时,我们并不鼓励多用宏。但写一些框架代码,为了提高使用上的表现力时,宏也会是一个利器。
如果有兴趣,欢迎讨论!