面向?qū)ο笤O(shè)計(jì)中颊艳,消息的傳遞是整個(gè)系統(tǒng)中非常重要的一環(huán)。ObjectC有runtime運(yùn)行時(shí)來(lái)負(fù)責(zé)消息的傳遞和處理媚媒。不過(guò)這里不打算說(shuō)oc的runtime锄码,先來(lái)看看微軟的天才工程師們是如何解決這個(gè)問(wèn)題触徐。
當(dāng)消息發(fā)生時(shí)咪鲜,尋找到合適的對(duì)象的方法去處理狐赡,這個(gè)過(guò)程很容易就聯(lián)想到面向?qū)ο笾械亩鄳B(tài)和繼承撞鹉。于是不假思索就得出一個(gè)解決方案。在基類(lèi)中定義好虛函數(shù)颖侄,有子類(lèi)去負(fù)責(zé)具體實(shí)現(xiàn)鸟雏。那樣大約會(huì)產(chǎn)生如下的代碼:
class CCmdTarget
{
.....
public:
virtual void OnDraw();
virtual void OnClick();
virtual void OnKeyBoard();
.....
};
對(duì)于每一個(gè)能想到的消息,都在基類(lèi)中寫(xiě)一個(gè)對(duì)應(yīng)的虛函數(shù)览祖,留作子類(lèi)去重寫(xiě)孝鹊。這樣問(wèn)題貌似得到了解決,不過(guò)效率不言而喻展蒂,每個(gè)對(duì)象都要持有一份長(zhǎng)長(zhǎng)的虛函數(shù)表又活。另外,如果將來(lái)有新的消息類(lèi)型還不得不在基類(lèi)中添加一個(gè)虛函數(shù)锰悼。
換個(gè)思路柳骄。消息映射,消息路由箕般。這些字眼都在提醒著我們耐薯,我們需要一張表,這張表記錄了每一條消息我們?cè)摻挥烧l(shuí)處理丝里。于是表里的條目應(yīng)該是這樣的結(jié)構(gòu)體(精簡(jiǎn)了結(jié)構(gòu)體曲初,只取了幾個(gè)重要的字段,名稱(chēng)還是原封不動(dòng)的沿襲了MFC杯聚。):
class CCmdTarget;
typedef void (CCmdTarget:: * AFX_PMSG)(void);
struct AFX_MSGMAP_ENTRY
{
int nMessage;
int nCode;
AFX_PMSG pfn;
};
pfn是一個(gè)函數(shù)指針臼婆,其余兩個(gè)是關(guān)于消息的。有了這樣一張表幌绍,當(dāng)有消息的時(shí)候目锭,我們只需要查找一遍表评汰,然后就可以知道該有哪個(gè)函數(shù)來(lái)處理。接下來(lái)我們?cè)倏紤]另一個(gè)問(wèn)題痢虹,當(dāng)某個(gè)消息當(dāng)前類(lèi)不打算處理被去,或者基類(lèi)中存在統(tǒng)一的處理辦法,我們想交由它來(lái)處理該怎么整奖唯?這里就涉及到自頂向下(由子類(lèi)到父類(lèi)的傳遞)的遞歸惨缆。大致是這樣的過(guò)程,在當(dāng)前類(lèi)中找處理函數(shù)丰捷,如果找不到坯墨,就到父類(lèi)的表中去找,還是找不到病往,繼續(xù),直到找到捣染,或者到達(dá)根類(lèi)依然找不到就拋棄或者其他處理。這就需要我們?cè)谧宇?lèi)中保存有到達(dá)父類(lèi)表的方法停巷。這就像鏈表耍攘,為了形成鏈表,我們需要在節(jié)點(diǎn)中保存后繼節(jié)點(diǎn)的指針畔勤。于是我們定義如下結(jié)構(gòu)體:
struct AFX_MSGMAP
{
const AFX_MSGMAP * (*pfnGetBaseMap)();
const AFX_MSGMAP_ENTRY * lpEntries;
};
第一個(gè)是獲得父類(lèi)表的函數(shù)指針蕾各,第二個(gè)就是 表的入口地址。接下來(lái)我們定義一個(gè)函數(shù)用來(lái)取得這些數(shù)據(jù)庆揪。
virtual const AFX_MSGMAP * GetMessageMap()const;
定義成虛函數(shù)的原因大家想必都明白式曲,為了獲得具體對(duì)象的路由表,這就是多態(tài)的思想缸榛。
const AFX_MSGMAP * CCmdTarget::GetMessageMap()const
{
return GetThisMessageMap();
}
函數(shù)直接返回了另一個(gè)函數(shù)的返回值吝羞,我們來(lái)看下這個(gè)函數(shù)又是如何定義的。
static const AFX_MSGMAP * GetThisMessageMap();
const AFX_MSGMAP * CCmdTarget::GetThisMessageMap()
{
static const AFX_MSGMAP_ENTRY _messageEntries[] =
{
{0,0,(AFX_PMSG)0}
};
static const AFX_MSGMAP messageMap = {
&CCmdTarget::GetThisMessageMap,&_messageEntries[0]
};
return & messageMap;
}
定義了局部靜態(tài)變量内颗,然后將直接將地址返回钧排。這種設(shè)計(jì)也是性能考慮。靜態(tài)變量只初始化一次就在進(jìn)程的整個(gè)生命周期里存在于內(nèi)存中起暮。如果要問(wèn)為啥要大費(fèi)周章的設(shè)計(jì)兩個(gè)函數(shù)卖氨,一個(gè)函數(shù)的使命竟然只是為了調(diào)用另外一個(gè)函數(shù)。我的思路是负懦,GetMessageMap設(shè)計(jì)成虛函數(shù)的用意很明顯筒捺,為了實(shí)現(xiàn)多態(tài),每個(gè)對(duì)象都要獲得自己的路由表纸厉,這種根據(jù)不同的對(duì)象有不同的實(shí)現(xiàn)就是多態(tài)的思想系吭。另外AFX_MSGMAP結(jié)構(gòu)體的第一個(gè)字段就是一個(gè)函數(shù)指針,這個(gè)函數(shù)指針是普通的函數(shù)指針颗品,而不是一個(gè)指向類(lèi)成員函數(shù)的指針肯尺。沒(méi)有設(shè)計(jì)成指向類(lèi)成員函數(shù)指針的原因是沃缘,類(lèi)成員函數(shù)的指針在調(diào)用時(shí)必須要加上對(duì)象地址。這就使得我們必須在每個(gè)對(duì)象中保存一個(gè)父類(lèi)對(duì)象的地址则吟。這顯然是不切實(shí)際的槐臀,對(duì)象都還沒(méi)創(chuàng)建出來(lái),我們?cè)趺慈ケ4孢@個(gè)地址呢氓仲。于是我們需要一個(gè)編譯期就能確定的函數(shù)地址水慨。這也就是static const AFX_MSGMAP * GetThisMessageMap()被定義為靜態(tài)函數(shù)的原因。因?yàn)槲覀冊(cè)诰幾g期就需要它的地址啊敬扛。我們將父類(lèi)的函數(shù)地址存放AFX_MSGMAP結(jié)構(gòu)體中晰洒。就可以實(shí)現(xiàn) 運(yùn)行時(shí)通過(guò)多態(tài)的GetMessageMap函數(shù)獲得一個(gè)AFX_MSGMAP結(jié)構(gòu)體,而這個(gè)結(jié)構(gòu)體中又有一個(gè)父類(lèi)的獲得AFM_MSGMAP的函數(shù)的指針啥箭。這就形成了一個(gè)反向鏈表結(jié)構(gòu)谍珊。
至于如何路由,我隨意寫(xiě)了一個(gè)函數(shù)急侥,可供參考:
void HandleMessage(int nMessage,int nCode)
{
const AFX_MSGMAP * msg_map = this->GetMessageMap();
const AFX_MSGMAP * (* pfnGetBaseMap)();
while(msg_map != NULL)
{
const AFX_MSGMAP_ENTRY * msg_entry = msg_map->lpEntries;
while ( !(msg_entry->nCode == 0 && msg_entry->nMessage== 0) )
{
if(msg_entry->nMessage == nMessage && msg_entry->nCode == nCode)
{
AFX_PMSG p2fn = msg_entry->pfn;
(this->*p2fn)();
}
msg_entry ++;
}
///refresh msg_map , base_msg_map
pfnGetBaseMap = msg_map->pfnGetBaseMap;
msg_map = pfnGetBaseMap==NULL?NULL:(*pfnGetBaseMap)();
}
}
像偽代碼一樣直白砌滞,就是在本類(lèi)的路由表里搜索一下 找不到就繼續(xù)去父類(lèi)找。這就是MFC消息映射的一點(diǎn)個(gè)人理解缆巧。下面貼一下 樣例代碼布持。僅供參考豌拙。至于DECLARE_MESSAGE_MAP BEGIN_MESSAGE_MAP這些宏就不說(shuō)了陕悬,那都不是重點(diǎn)。
class CCmdTarget;
typedef void (CCmdTarget:: * AFX_PMSG)(void);
struct AFX_MSGMAP_ENTRY
{
int nMessage;
int nCode;
AFX_PMSG pfn;
};
struct AFX_MSGMAP
{
const AFX_MSGMAP * (*pfnGetBaseMap)();
const AFX_MSGMAP_ENTRY * lpEntries;
};
class CCmdTarget
{
protected:
static const AFX_MSGMAP * GetThisMessageMap();
virtual const AFX_MSGMAP * GetMessageMap()const;
public:
void HandleMessage(int nMessage,int nCode)
{
const AFX_MSGMAP * msg_map = this->GetMessageMap();
const AFX_MSGMAP * (* pfnGetBaseMap)();
while(msg_map != NULL)
{
const AFX_MSGMAP_ENTRY * msg_entry = msg_map->lpEntries;
while ( !(msg_entry->nCode == 0 && msg_entry->nMessage== 0) )
{
if(msg_entry->nMessage == nMessage && msg_entry->nCode == nCode)
{
AFX_PMSG p2fn = msg_entry->pfn;
(this->*p2fn)();
}
msg_entry ++;
}
///refresh msg_map , base_msg_map
pfnGetBaseMap = msg_map->pfnGetBaseMap;
msg_map = pfnGetBaseMap==NULL?NULL:(*pfnGetBaseMap)();
}
}
private:
void OnDraw()
{
cout << "this is CCmdTarget::OnDraw()" << endl;
}
};
const AFX_MSGMAP * CCmdTarget::GetMessageMap()const
{
return GetThisMessageMap();
}
const AFX_MSGMAP * CCmdTarget::GetThisMessageMap()
{
static const AFX_MSGMAP_ENTRY _messageEntries[] =
{
{100,100,(AFX_PMSG)&CCmdTarget::OnDraw},
{0,0,(AFX_PMSG)0}
};
static const AFX_MSGMAP messageMap = {
/*&CCmdTarget::GetThisMessageMap*/NULL,&_messageEntries[0]
};
return & messageMap;
}
//////////////////////////////////////////////////////////////
class CWind:public CCmdTarget
{
protected:
static const AFX_MSGMAP * GetThisMessageMap();
virtual const AFX_MSGMAP * GetMessageMap()const;
};
const AFX_MSGMAP * CWind::GetMessageMap()const
{
return GetThisMessageMap();
}
const AFX_MSGMAP * CWind::GetThisMessageMap()
{
static const AFX_MSGMAP_ENTRY _messageEntries[] =
{
{0,0,(AFX_PMSG)0}
};
static const AFX_MSGMAP messageMap = {
&CCmdTarget::GetThisMessageMap,&_messageEntries[0]
};
return & messageMap;
}
int main(int argc, const char * argv[])
{
CWind win7;
win7.HandleMessage(100, 100);
}