转载请注明出处:
http://blog.csdn.net/herm_lib/article/details/8223501
本文分别用AS3 Function和C++/boost实现我们经常用到的回调功能,实现一个C/S程序经常用到的消息分发器来简要地对比一下两者实现的难易程度和效果。主要的篇幅会在C++实现回调的这块,而AS3 Function对回调完美地支持,让一切变得很简单。C++这块先用接口和成员函数指针实现一个简单的回调机制;接下来,为了让回调处理函数更灵活,能写出更优美的代码,我们引入boost::function和很复杂的boost::bind(实现代码看得好累)实现一个消息分发器。
对于长期用C++做项目的人来说,用AS3 Function实现回调功能感觉很自然也很简单。AS3 Function对回调机制的支持,不考虑运行效果的话,我个人认为是完美的。对于编译时类型就确定的静态语言C++来说,就个人对C++的理解程度,感觉从理论应该无法实现和AS3 Function同等的效果(支持完整性和调用的简单性)。
先简单说一下回调机制,C语言一般用函数指针去实现回调。C++可以用函数指针、类的静态函数指针、类的成员函数指针以及虚函数。其中函数指针和类的静态函数指针本质上一样的。
先简述一下消息分发器。消息分发器就是根据消息唯一标示(命令ID、字符串等),将不同的消息内容分发给不同的逻辑处理器的独立模块;从设计角度,我们一般将置于逻辑层下面,就是说,我们的消息分发器肯定是不要知道逻辑层对象的细节的。这个时候,我们就要用常用的回调机制了。我们就用虚函数和类的成员函数指针来实现一个回调的功能。
假设我们正在做一个代号为"XNN"。
我们不知道逻辑处理Handler的细节,先给出一个Handler的接口IHandler。
struct IHandler { virtual void OnHandle1() = 0; virtual void OnHandle2() = 0; }; typedef void (IHandler::*MemFunPtr)();
我们实现一个底层的Session类,用来收发数据。
struct XSession { void Register(int type, int code, IHandler* handler, MemFunPtr fp) { m_handler = handler; m_fp = fp; } void DoSomeData() { (m_handler->*m_fp)(); } private: IHandler* m_handler; MemFunPtr m_fp; };
XSession根据Register的type&code找到handler,然后具体的Handler对象将能处理数据。
定义一下协议命令:
namespace XNN{ enum CmdType { CMD_TYPE_AUTH = 1, CMD_TYPE_ROLE = 2, }; enum CmdCodeAuth { CMD_CODE_AUTH_SIGN_REQ = 1, CMD_CODE_AUTH_SIGN_RES = 2, CMD_CODE_AUTH_HELLO = 3, }; enum CmdCodeRole { CMD_CODE_ROLE_GET_REQ = 1, CMD_CODE_ROLE_GET_RES = 2, CMD_CODE_MODIFY_REQ = 3, }; }
我们协议定义规则是,先按Type分大类,然后按Type分出小类Code。我们目标是希望是一个Handler对象里几个成员函数能处理一个Type下所有Code协议。看看我们能不能做到这个目标。我们实现具体的Handler,并且调起来。
XSession session; // 用AuthHandler处理CMD_TYPE_AUTH的协议 AuthHandler authHandler; session.Register(XNN::CMD_TYPE_AUTH, XNN::CMD_CODE_AUTH_SIGN_REQ, &authHandler, &IHandler::OnHandle1); session.DoSomeData(); session.Register(XNN::CMD_TYPE_AUTH, XNN::CMD_CODE_AUTH_HELLO, &authHandler, &IHandler::OnHandle2); session.DoSomeData(); // 用RoleHandler处理CMD_TYPE_ROLE的协议 RoleHandler roleHandler; session.Register(XNN::CMD_TYPE_ROLE, XNN::CMD_CODE_ROLE_GET_REQ, &roleHandler, &IHandler::OnHandle1); session.DoSomeData();
我们目标达到了吗?我们只能说算是达到了,代码可读很差。OnHandle之类没法正常命名。类似我们这类需求,正常情况下要放弃上面的方案,寻找另外一种更灵活的方案。
我们下面借助boost::function&bind比较自然地实现目标。
先看一下我们基础的分发器的样子。我实际实现的分发是基于模板,不同的项目应用可以传不同的消息数据类型和参数类型:
template <class HContent, class HParam> class Dispatcher;
我们把Dispatcher简化掉,只保留HContent,而且不用模板了,直接写死。
namespace Herm{ class Dispatcher { public: typedef std::tr1::function<bool(const XNN::MsgContent&)> Handler; typedef std::vector<std::pair<int, Handler> > Handlers; private: typedef std::map<int, Handlers> Type2Handlers; public: void Register(int type, int code, Handler handler) { Type2Handlers::iterator it = m_handlers.find(type); if (it != m_handlers.end()) { if (IsHandlerValid(it->second, code, handler)) it->second.push_back(std::make_pair(code, handler)); } else { Handlers handlers; handlers.push_back(std::make_pair(code, handler)); m_handlers.insert(std::make_pair(type, handlers)); } } void Dispatch(int type, int code, const XNN::MsgContent& mc) { const Handlers* handlers = GetHandlers(type); if (handlers) { for (size_t i = 0; i < handlers->size(); i++) { if ((*handlers)[i].first == code) (*handlers)[i].second(mc); } } } private: bool IsHandlerValid(const Handlers& handlers, int code, Handler handler) const { for (size_t i = 0; i < handlers.size(); i++) { if (handlers[i].first == code && handlers[i].second == handler) return false; } return true; } const Handlers* GetHandlers(int type) { Type2Handlers::iterator it = m_handlers.find(type); if (it != m_handlers.end()) return &(it->second); return NULL; } private: Type2Handlers m_handlers; }; }
boost::function/bind已经加入到tr1, VC++2008已经支持tr1,例子就直接用tr1了。
现在我们的Handler变成了,
typedef std::tr1::function<bool(const XNN::MsgContent&)> Handler;
返回值是bool, 带有一个参数XNN::MsgContent的一个函数对象。我们通过这个Handler来注册和分发。
看我们是怎么注册的。
XNN::Sign sign; Herm::Dispatcher disp; disp.Register(XNN::CMD_TYPE_AUTH, XNN::CMD_CODE_AUTH_SIGN_REQ, std::tr1::bind(&XNN::Sign::SignRes, // 要响应的成员函数 &sign, // 对应的对象 std::tr1::placeholders::_1 // 参数保留位,和SignRes参数个数对应 )); disp.Register(XNN::CMD_TYPE_AUTH, XNN::CMD_CODE_AUTH_HELLO, std::tr1::bind(&XNN::Sign::Hello, &sign, std::tr1::placeholders::_1)); XNN::Role role; disp.Register(XNN::CMD_TYPE_ROLE, XNN::CMD_CODE_ROLE_GET_REQ, std::tr1::bind(&XNN::Role::GetReq, &role, std::tr1::placeholders::_1));
我们通过tr1::bind()把类的成员函数注册给handler(tr1::function<>的)。如果注册全局函数的,直接传地址就行了。
实际例子中,TYPE_AUTH下的CODE协议分发给Sign对象;TYPE_ROLE下的CODE协议分发给Role对象,他们各自的成员函数作为逻辑的Handler来处理各自消息。很清晰。我们目标达成了。
和上面接口和成员函数指针实现方式比较,这种方式的优点是更灵活,Handler不须要强制从IHandler继承了,同时可以定义任意类的成员函数处理自己想处理的协议消息。缺点的话,Register调用复杂了很多,表现在用tr1::bind容易出错,一出错,一大堆报错。
C++处理分发的需求,借助了强大的外力,才得到比较优美的处理方案。下面AS3登场,我们将看到事情变得很直接很简单,不须要我们思考。
AS3的分发器:
public class MsgDispatcher { private var _cmdHandlerMap:Dictionary; public function MsgDispatcher() { _cmdHandlerMap = new Dictionary(); } public function register(type:int, code:int, handler:Function):void { var codeHandlers:Vector.<CodeHandlerPair> = _cmdHandlerMap[type]; if (codeHandlers == null) { codeHandlers = new Vector.<CodeHandlerPair>(); _cmdHandlerMap[type] = codeHandlers; } // 忽略一样的code&handler if (!isHandlerValid(code, handler, codeHandlers)) return; codeHandlers.push(new CodeHandlerPair(code, handler)); } private function isHandlerValid(code:int, handler:Function, handlers:Vector.<CodeHandlerPair>):Boolean { for (var i:int = 0; i < handlers.length; i++) { if (handlers[i]._code == code && handlers[i]._handler == handler) return false; } return true; } public function dispatch(type:int, code:int, msgBody:MsgBody=null):void { var codeHandlers:Vector.<CodeHandlerPair> = _cmdHandlerMap[type]; if (codeHandlers == null) return; for (var i:int = 0; i < codeHandlers.length; i++) { if (codeHandlers[i]._code == code) codeHandlers[i]._handler(msgBody); } } }
Handler就用AS3提供的Function,我们什么都不用做,全局函数,任意类的成员函数都可以做为
function register(type:int, code:int, handler:Function)
的handler参数注册到Dispatcher中。像这样调用就行了:
msgDispatcher.register(XNN::CMD_TYPE_AUTH, XNN::CMD_CODE_AUTH_SIGN_REQ, onAuthReq); // 这里onAuthReq就类的一个成员函数
相比C++,AS3更灵活,分发器和Handler可以约定任意参数类型和参数,同时用起来也更简单明了。
结语
为什么同样的需求,在不同语言下面复杂度呈现出如此大的区别呢?其他方面我没去学习,不懂。有一点可以肯定的是和C++设计原则有关系,C++要静态地编译时检查错误,而且尽量避免支持运行时的性能损耗的特性。类似AS3 Function运行时做各种检查的特性C++设计者是不允许的出现的。语言在于应用范围,没有优劣之别。
附:支持两个模板参数的Dispatch(CSDN blog不能传附件真不方便)。
template <class HContent, class HParam> class Dispatcher { public: typedef std::tr1::function<bool(const HContent&, HParam*)> Handler; typedef std::vector<std::pair<int, Handler> > Handlers; private: typedef std::map<int, Handlers> Type2Handlers; public: bool IsHandlerValid(const Handlers& handlers, int code, Handler handler) const { for (size_t i = 0; i < handlers.size(); i++) { if (handlers[i].first == code && handlers[i].second == handler) return false; } return true; } void Register(int type, int code, Handler handler) { Type2Handlers::iterator it = m_handlers.find(type); if (it != m_handlers.end()) { if (IsHandlerValid(it->second, code, handler)) it->second.push_back(std::make_pair(code, handler)); } else { Handlers handlers; handlers.push_back(std::make_pair(code, handler)); m_handlers.insert(std::make_pair(type, handlers)); } } const Handlers* GetHandlers(int type) { Type2Handlers::iterator it = m_handlers.find(type); if (it != m_handlers.end()) return &(it->second); return NULL; } private: Type2Handlers m_handlers; }; template <class HContent, class HParam> class MsgDispatcher : public Herm::Dispatcher<HContent, HParam> { public: void Dispatch(int type, int code, const HContent& mc, HParam* param) { const Handlers* handlers = GetHandlers(type); if (handlers) { for (size_t i = 0; i < handlers->size(); i++) { if ((*handlers)[i].first == code) (*handlers)[i].second(mc, param); } } } };