一段时间闲的慌,终日无所事事,每天罪恶感爆棚。原来闲下来的日子也不好过…
问题描述
最近一朋友在开发数据库模块时遇到一些问题:老大抛给他的是一个接口,录入的数据类型是不固定的,话说有上百种的样子;由于实习没经验不知如何下手。到底怎么做能够支持这么多种数据的解析处理,使得维护方便,扩展性又好呢?
需求分析
上层调用只需调用一次接口,并且接口不可改,数据类型不确定。
示例:
//统一接口
void process(void* data);
int main()
{
int data = 0;
process(&data);
}
解决方法
从中可以看出,对于处理每一种数据,处理方法应该是不同的,但是接口要一样。这样好办,使用函数指针能够解决问题。
//统一接口
typedef void (*process)(void* data);
void method_int(void* data){
int* a = (int*) data;
//TODO
}
void method_short(void* data){
short* a = (short*) data;
//TODO
}
int main()
{
int data = 0;
process pfun = method_int;
pfun(&data);
short sdata = 0;
pfun = method_short;
pfun(&sdata);
//...
}
但是这样存在很大的问题,上层应用需要根据具体的类型去修改指针指向,这多累啊!维护起来也很麻烦。 这里存在的改进空间还是很多的。
- 底层改变不应该去修改上层应用
- 实现函数分散不易管理
改进优化
注意到一个点就是每次改变数据时,都是通过改变函数指针的指向从而调用不同的处理方法。有了这点发现,就可以处理成一张表格,每一种数据类型绑定一种处理方法。这在C++中处理起来也很方便,std::map
可以很方便的帮我们做这件事情。
#include <map>
//具体实现
typedef void* pBobj;
typedef void(*pfun)(void*);
//具体数据类型对应编号
enum Type
{
DataA,
DataB,
};
//具体实现
void processA(pBobj data)
{
cout << "process1" << endl;
}
void processB(pBobj data)
{
cout << "process2" << endl;
}
//全局维护函数表
static map<Type, pfun> fun_map;
void map_init()
{
fun_map.insert(pair<Type, pfun>(Type::DataA, processA));
fun_map.insert(pair<Type, pfun>(Type::DataB, processB));
//每次添加一种数据,只需要在该函数中再添加绑定的过程
}
//剩下就是查表回调的工作
void adaptProcess(Type type, pBobj data)
{
map<Type, pfun>::iterator iter = fun_map.begin();
for (; iter != fun_map.end(); iter++)
{
if (iter->first == type)
{
iter->second(data);
}
}
}
至此,顶层应该只需调用初始化和查表函数即可。
int main()
{
Type type = ReadType();
map_init();
adaptProcess(type,'c');
}
总结
通过维护全局的函数指针表与对应的枚举变量,可以方便添加针对不同消息的响应处理,这在游戏客户端处理服务端多种消息时采用回调不同的处理函数是比较常见的。并且维护与扩展性都比较好。坏处是添加一定内存开销,并且每次都需要查表,这也消耗了一定的处理时间。不过C++底层map容器采用的是红黑树,数据搜索复杂度维持在 $log_2N$
。