我目前负责的模块File Browser是专门管理网络文件的软件,用户对于远程电脑文件的处理都是通过发送PDU进行的。因为支持的功能繁多,所以PDU的类型也很多,由于PDU处理机制过程比较复杂,而老的处理方法又很不灵活,导致添加新的PDU类型,以及对现有PDU类的改动比较麻烦,有牵一发而动全身的感觉。所以有必要对此机制加以优化改良。
我们先看看PDU接受的过程:Fiel Browser从 ARM层得到数据包,根据其类型创建相应的PDU对象,然后再将此对象作为参数传送给相应的派发函数,最终由交给统一的处理函数。如下图:
CFsRemoteView::DataIndication |
CFsRemoteView:HandleFileOfferPdu CFsRemoteView:HandleFileAcceptPdu 。。。 |
CFsApp:: OnCommand |
阅读一下相关的函数代码实现:
int CFsRemoteView:: DataIndication(short priority, LPBYTE lpData, DWORD dwLength)
{
。。。
CDvMemArchive ar((LPBYTE)lpData, dwLength);
WORD wType;
ar >> wType;
CDvPdu* pPdu = NULL;
switch ((int)wType)
{
case FS_PDU_FILE_OFFER:
WBXLOG_SP(”CFsRemoteView:: DataIndication:FS_PDU_FILE_OFFER”);
pPdu = new CFsFileOfferPdu;
pPdu->SerializeFrom(ar);
HandleFileOfferPdu((CFsFileOfferPdu*)pPdu);
break;
case FS_PDU_FILE_ACCEPT:
WBXLOG_SP(”CFsRemoteView:: DataIndication:FS_PDU_FILE_ACCEPT”);
pPdu = new CFsFileAcceptPdu;
pPdu->SerializeFrom(ar);
HandleFileAcceptPdu((CFsFileAcceptPdu*)pPdu);
break;
。。。
}
if (pPdu)
delete pPdu;
return 0;
}
void CFsRemoteView::HandleFileOfferPdu(CFsFileOfferPdu* pPdu)
{
m_pCmdTarget->OnCommand(NID_RV_FILE_OFFER, (LPARAM)pPdu);
}
void CFsRemoteView::HandleFileAcceptPdu(CFsFileAcceptPdu* pPdu)
{
m_nFlowControl = TRUE;
m_pCmdTarget->OnCommand(NID_RV_FILE_ACCEPT, (LPARAM)pPdu);
}//void CFsRemoteView::HandleFileAcceptPdu(CFsFileAcceptPdu* pPdu)
。。。
可以看到CFsRemoteView:: DataIndication函数采用了switch case这种常被人所垢病的语句,由于PDU Class的数目很多(约30种)。所以整个函数代码非常的多(超过300行),复杂度也比较大(43),难以阅读理解,而且针对每个PDU类都要有相应的成员函数进行处理。另外由于它的实作与数据绑定,必须知道PDU所有的Classes,导致代码非常的难以维护::如果想处理一个新的PDU Class,那么除了必须要修改CFsRemoteView:: DataIndication,而且还要为此 PDU Class的处理添加一个新的成员函数。
毋庸置疑,改良这个函数的关键在于重构switch case部分语句。
仔细阅读一下代码,我们发现switch case语句各个case之前不同的主要有两点:
1)创建一个PDU对象的语句。
2) 不同的处理PDU对象的成员函数。
针对这两点,我设计了一个新的处理机制,大致代码如下:
CDvPdu*CreateOfferPdu{}{ return new CFsFileOfferPdu; }
CDvPdu* CreateAcceptPdu {}{ return new CFsFileAcceptPdu; }
。。。
typedef CDvPdu* (*PNewPdu)(void);
typedef void (CFsRemoteView::*PCommander)();
typedef struct st_PduHandler
{
int nPduType;
int nCmdType;
PNewPdu pNewPdu;
PCommander pCommander;
}STPduHandler, *PSTPduHandler;
STPduHandler g_stPduHandlers[] =
{
{FS_PDU_FILE_OFFER, NID_RV_FILE_OFFER, CreateOfferPdu, NULL},
{FS_PDU_FILE_ACCEPT,NID_RV_FILE_ACCEPT,CreateAcceptPdu,CFsRemoteView::CommanderFile}
。。。
}
int CFsRemoteView:: DataIndication(short priority, LPBYTE lpData, DWORD dwLength)
{
。。。
CDvMemArchive ar((LPBYTE)lpData, dwLength);
WORD wType;
ar >> wType;
for (int i = 0; i < NELEMS(g_stPduHandlers); i++)
{
if (g_stPduHandlers[i].nPduType == wType)
{
CDvPdu* pPdu = g_stPduHandlers[i].pNewPdu();
pPdu->SerializeFrom(ar);
if (g_stPduHandlers[i].pCommander)
{
(this->*(g_stPduHandlers[i].pCommander))();
}
m_pCmdTarget->OnCommand(g_stPduHandlers[i].nCmdType,
(LPARAM)pPdu);
delete pPdu;
}
}
。。。
Return 0;
}
void CFsRemoteView::CommanderFile()
{
m_nFlowControl = TRUE;
}
。。。
改动之后CFsRemoteView:: DataIndication仅仅只有40行左右,另外仅用2个成员函数就取代了以前近30个处理PDU Classes的成员函数.复杂度达到可以忽略不计。而且实现了实作与数据分离,添加删除任意的PDU Class都不需要修改此函数。
但是这个设计还是有稍稍不尽如人意的地方,那就是每个PDU Class都对应一个创建函数.几十个这样的函数堆在一起,看起来也很头疼.维护起来也还是没有达到“一行代码”的终极简便,怎么去简化这一步?
由于所有的PDU创建函数,都只是类型不用,所以很自然的想到采用模板来处理.
template<Classes T>
CDvPdu* NewPdu()
{
return New T;
}
STPduHandler g_stPduHandlers[] =
{
{FS_PDU_FILE_OFFER,ID_RV_FILE_OFFER,&NewPdu<CFsFileOfferPdu>, NULL},
。。。
}
可编译的时候,却遇上了错误,仔细检查原因才发现:
要想定义一个指向模板函数的函数指针,该指针也只能定义为一个基于模板的变量.
但模板只支持函数和类.所以我这里得使用struct为中间层,再次修改代码如下:
template<class T>
struct Creator
{
static CDvPdu* NewPdu()
{
return new T;
}
};
STPduHandler g_stPduHandlers[] =
{
{FS_PDU_FILE_OFFER, NID_RV_FILE_OFFER,
Creator <CFsFileOfferPdu>::NewPdu, NULL},
{FS_PDU_FILE_ACCEPT,NID_RV_FILE_ACCEPT,
Creator <CFsFileAcceptPdu>::NewPdu, CFsRemoteView::CommanderFile},
。。。
}
重新编译测试,结果正确.