用過C++進行過面向對象程序設計的用戶都知道,程序中的對象很少單獨存在。不考慮對象間的相互作用幾乎是不可能的。所以,標識對象間的關係或建立對象間的消息連接是面向對象程序設計的一項重要任務。本文着重從C++程序設計的角度,提出一種建立對象間消息連接的實用方法。如果你想詳細瞭解面向對象程序設計技術,請參閱有關專著。
大家都知道對象是數據和方法的封裝體。在C++中,它們分別表現爲數據成員和成員函數。程序設計者通過執行對象的各種方法,來改變對象的狀態(即改變對象的屬性數據)。從而使該對象發生某些“事件”。當一對象發生某事件時,它通常需向其它相關對象發送“消息”,請求它們作出一些處理。 這時,發生事件並向其它對象請求處理的對象被稱爲“事件對象”,而處理事件的對象被稱爲“回調對象”。回調對象對事件的處理稱爲“回調函數”。
在C++中,這一過程相當於:當事件對象發生事件時,調用回調對象的某些成員函數。通常的作法是回調對象向事件對象傳遞對象指針。但這種方法不通用。爲了減少程序設計的工作量,本文提出一種建立對象間消息連接的系統方法。它的思路是:將“事件發生→請求處理→執行處理”這一過程抽象成一個“回調”(CallBack)類。通過繼承,用戶可以輕鬆獲取建立對象間消息連接的機制。
一、回調類的數據結構及其成員函數
本文提出的CallBack類支持三種回調函數。它們是:回調對象中的成員函數,屬於回調類的靜態成員函數和普通的C函數。CallBackle類中包含一回調函數表callBackList。它用於記錄事件名稱,指向回調函數及回調對象的指針。該表的每一個節點爲一個事件記錄EventRecord。每個事件記錄包含三個域:事件名指針eventName,指向回調對象的指針pointerToCBO,指向回調函數的指針pointerToCBF或pointerToCBSF(其中,pointerToCBF指向回調對象的成員函數,pointerToCBSF指向回調類的靜態成員函數或普通函數。它們同處於一共用體內)。CallBack類所提供的回調機制是這樣的:在事件對象上註冊回調對象中的回調函數;當事件發生時,事件對象在其回調錶中檢索並執行回調函數。從而使二者的消息連接得以建立。(關於該類的具體實現,請參閱文後所附的程序清單) 回調對象
事件對象
事件名 回調對象指針 回調函數指針
“event” pointerCBO pointerToCBF或 pointerTOCBSF - - - - - - |
AddCallBack: 註冊事件名和指向回調函數,回調對象的指針
CallCallBack: 在回調錶中,檢索註冊在指定事件上回調函數並調用它們
事件發生時,調用CallCallBack函數
對事件event進行處理的成員函數
從CallBack類繼承的回調錶callBackList, 成員函數AddCallBack和CallCallBack。
當回調函數爲靜態成員函數或普通C函數時, pointerToCBO爲NULL。
事件名是回調錶callBackLis中的檢索關鍵字。
回調對象中其它成員函數
CallBack類的成員函數AddCallBack用來將回調函數註冊到事件對象的回調錶中。它有兩個重載版本:
void CallBack::AddCallBack(char *event,CallBackFunction cbf,CallBack *p); void CallBack::AddCallBack(char *event,CallBackStaticFunction cbsf); |
其中,第一個AddCallBack用來將某回調對象的成員函數註冊到事件對象的回調錶中。第二個AddCallBack用來將或某回調類的靜態成員函數註冊到事件對象的回調錶中。在上參數表中,event是指向事件名字符串的指針,p是指向回調對象的指針,cbf和cbsf分別是指向成員函數及靜態成員函數(或普通函數)的指針。當回調函數來自某回調對象SomeObject時,傳遞成員函數指針應採用如下格式:
(CallBackFunction)&SomeObject::MemberFunctionName; 傳遞SomeObject類的某靜態成員函數指針應採用格式: (CallBackStaticFunction)& SomeObject::FunctionName;傳遞程序中普通函數指針時,只需傳遞函數名即可。 |
CallBack類的成員函數void CallBack::CallCallBack(char *ename, CallData calldata = NULL)用來調用註冊在事件ename上的所有回調函數。其中,calldata爲數據指針(CallData實際上就是void*,詳見程序清單)。事件對象可通過它向回調對象傳遞有用的數據。該成員函數通常在事件對象的成員函數中調用,因爲通常只有事件對象的成員函數才能改變對象的內部數據,從而使某些事件發生。
成員函數RemoveCallback用來刪除註冊在事件對象上的回調函數。它的三個重載版本依次爲:
void CallBack::RemoveCallBack(char *event,CallBackFunction cbf,CallBack *p); void CallBack::RemoveCallBack(char *event,CallBackStaticFunction cbsf); void CallBack::RemoveCallBack(char *event); |
其中,event,cbf,cbsf,p等參數和成員函數AddCallBack中各參數一樣。第一個RemoveCallBack用於刪除註冊在事件event上某回調對象的一個成員函數。第二個RemoveCallBack用於刪除註冊在事件event上的某普通函數或某回調類的一個靜態成員函數。第三個RemoveCallBack用於刪除註冊在事件event上的全部回調函數。
二、CallBack類的使用方法
使用CallBack類,可按以下步驟進行:
1.確定程序中哪些對象間存在關係,需要建立消息連接。並確定在各特定消息連接關係中,哪個對象是事件對象,哪個對象是回調對象。
2.事件對象類和回調對象類都必須從CallBack類繼承,以獲得回調支持。
3.爲事件對象註冊回調數據。包括:事件名,回調函數名,指向回調對象的指針。
4.當你感興趣的事件發生時,在事件對象類引發事件的成員函數中調用CallCallBack函數。
下面是一個具體的例子。通過它你會對Callback類的使用方法有進一步的瞭解。
file://測試程序文件:test.cpp #include"callback.h" file://“揚聲器”類 class Speaker:public CallBack { private: int volume; public: Speaker(int v): volume(v) {} void IncreaseVolume(int v) file://增加音量成員函數 { volume += v; if(volume > 20){ file://“音量大於20”事件發生了 file://調用註冊在兩事件上的回調函數 CallCallBack("音量改變了"); CallCallBack("音量大於20", &volume); } } void DecreaseVolume(int v) file://降低音量成員函數 { volume -= v; if(volume < 5){ file://“音量小於5”事件發生了 file://調用註冊在兩事件上的回調函數 CallCallBack("音量改變了"); CallCallBack("音量小於5", &volume); } } }; file://“耳朵”類 class Ear : public CallBack { public: static void Response(CallData callData) file://對“音量改變”的反應 { cout<<"音量改變了."<<endl; } void HighVoiceResponse(CallData callData)//對高音的反應 { cout<<”喂!太吵了!現在音量是:"<<*((int *)callData)<<endl; } void LowVoiceResponse(CallData callData)// 對低音的反應 { cout<<"啊!我聽不清了。現在音量是:"<<*((int *)callData)<<endl; } }; void main(void) { Speaker s(10); file://現在音量爲10 Ear e; file://爲事件對象s註冊回調函數 s.AddCallBack("音量大於20”,(CallBackFunction)&Ear::HighVoiceResponse,&e); s.AddCallBack("音量小於5”,(CallBackFunction)&Ear::LowVoiceResponse,&e); s.AddCallBack("音量改變了",(CallBackStaticFunction)&Ear::Response); s.IncreaseVolume(12);//將音量增加12,現在音量位22 s.DecreaseVolume(20);//將音量減少20,現在音量位2 } |
運行結果:
音量改變了.
喂!太吵了!現在音量是:22
音量改變了.
啊!我聽不清了。現在音量是:2
在上例中,揚聲器對象s爲事件對象,耳朵對象e爲回調對象。。s上被註冊了三個事件:“音量改變了”,“音量大於20”,“音量小於5”。 回調函數分別爲:Ear::Response, Ear::HighVoiceResponse,Ear::LowVoiceResponse。當揚聲器s通過其成員函數IncreaseVolume和 DecreaseVolume改變音量時,回調對象e會自動作出反應。可見,通過使用CallBack類,在對象間建立消息連接已變爲一項很簡單和優美的工作。
由於筆者水平有限,該類的設計必有不完善之處。如果您對它感興趣,筆者可與各位C++玩家共同探討這類問題。
附:程序清單(本程序在MS VC++5.0和TC++3.0上均編譯通過)
file://回調類的類結構:callback.h #ifndef _CALLBACK_H #define _CALLBACK_H #include<stdlib.h> #include<string.h> #include<iostream.h> #define CALLBACKLIST_INIT_SIZE 10 #define CALLBACKLIST_INCREMENT 5 class CallBack; typedef void *CallData;//回調數據指針類型定義 typedef void (CallBack::*CallBackFunction)(CallData); file://指向回調成員函數的指針 typedef void (*CallBackStaticFunction)(CallData); file://指向靜態成員函數或普通函數的指針類型定義 class EventRecord{ private: char *eventName; file://回調事件名稱 CallBack *pointerToCBO;//指向回調對象的指針 file://指向成員函數的指針和指向靜態成員函數(或普通函數)指針的共用體 union{ CallBackFunction pointerToCBF; CallBackStaticFunction pointerToCBSF; }; public: EventRecord(void); file://事件記錄類的缺省構造函數 file://構造包含成員函數的事件記錄 EventRecord(char *ename,CallBack *pCBO,CallBackFunction pCBF); file://構造包含靜態成員函數或普通函數的事件記錄 EventRecord(char *ename,CallBackStaticFunction pCBSF); ~EventRecord(void);//析構事件記錄 void operator = (const EventRecord& er);//重載賦值運算符 file://判斷當前事件記錄的事件名是否爲ename int operator == (char *ename) const; file://判斷當前事件記錄是否和指定事件記錄相等 int operator == (const EventRecord& er) const; void Flush(void); file://將當前事件記錄清空 int IsEmpty(void) const;//判斷事件記錄是否爲空(即事件名是否爲空) friend class CallBack; file://讓CallBack類能訪問EventRecord的私有成員; }; class CallBack { private: EventRecord *callBackList; file://回調事件表 int curpos; file://當前事件記錄位置 int lastpos; file://回調錶中最後一空閒位置 int size; file://回調錶的大小 void MoveFirst(void) { curpos = 0; }//將當前記錄置爲第一條記錄 void MoveNext(void) file://將下一條記錄置爲當前記錄 { if(curpos == lastpos) return; curpos++; } file://判斷回調錶是否被遍歷完 int EndOfList(void) const { return curpos == lastpos; } public: CallBack(void);//構造函數 CallBack(const CallBack& cb);//拷貝構造函數 ~CallBack(void);//析構函數 void operator = (const CallBack& cb);// 重載賦值運算符 file://將回調對象的成員函數、靜態成員函數(或普通函數) file://註冊爲事件對象的回調函數 void AddCallBack(char *event,CallBackFunction cbf,CallBack *p); void AddCallBack(char *event,CallBackStaticFunction cbsf); file://刪除註冊在指定事件上的回調函數 void RemoveCallBack(char *event,CallBackFunction cbf,CallBack *p); void RemoveCallBack(char *event,CallBackStaticFunction cbsf); void RemoveCallBack(char *event);// 刪除某事件的全部記錄 file://執行註冊在某一事件上的所有回調函數 void CallCallBack(char *event, CallData calldata = NULL); }; #endif file://回調類的實現:callback.cpp #include"callback.h" file://EventRecord類的實現 EventRecord::EventRecord(void) { eventName = NULL; pointerToCBO = NULL; file://因爲sizeof(CallBackFunction) > sizeof(CallBackStaticFunction) pointerToCBF = NULL; } EventRecord::EventRecord(char *ename, CallBack *pCBO, CallBackFunction pCBF) :pointerToCBO(pCBO), pointerToCBF(pCBF) { eventName = strdup(ename); } EventRecord::EventRecord(char *ename, CallBackStaticFunction pCBSF) :pointerToCBO(NULL), pointerToCBSF(pCBSF) { eventName = strdup(ename); } EventRecord::~EventRecord(void) { if(eventName) delete eventName; } void EventRecord::operator = (const EventRecord& er) { if(er.eventName) eventName = strdup(er.eventName); else eventName = NULL; pointerToCBO = er.pointerToCBO; pointerToCBF = er.pointerToCBF; } int EventRecord::operator == (char *ename) const { if((eventName == NULL)||ename == NULL) return eventName == ename; else return strcmp(eventName,ename) == 0; } int EventRecord::operator == (const EventRecord& er) const { return (er == eventName) /*er和eventname不能交換位置*/ &&(pointerToCBO == er.pointerToCBO) &&(pointerToCBO ? (pointerToCBF == er.pointerToCBF): (pointerToCBSF == er.pointerToCBSF)); } void EventRecord::Flush(void) { if(eventName){ delete eventName; eventName = NULL; } pointerToCBO = NULL; pointerToCBF = NULL; } int EventRecord::IsEmpty(void) const { if(eventName == NULL) return 1; else return 0; } file://Callback類的實現 CallBack::CallBack(void) { file://按初始尺寸爲回調錶分配內存空間 callBackList = new EventRecord[CALLBACKLIST_INIT_SIZE]; if(!callBackList){ cerr<<"CallBack: memory allocation error."<<endl; exit(1); } size = CALLBACKLIST_INIT_SIZE; lastpos = 0; curpos = 0; } CallBack::CallBack(const CallBack& cb): curpos(cb.curpos),lastpos(cb.lastpos),size(cb.size) { callBackList = new EventRecord[size]; if(!callBackList){ cerr<<"CallBack: memory allocation error."<<endl; exit(1); } file://一一複製各條事件記錄 for(int i = 0; i < size; i++) callBackList[i] = cb.callBackList[i]; } void CallBack::operator = (const CallBack& cb) { curpos = cb.curpos; lastpos = cb.lastpos; size = cb.size; delete [] callBackList;//刪除舊的回調錶 callBackList = new EventRecord[size];//重新分配內存空間 if(!callBackList){ cerr<<"CallBack: memory allocation error."<<endl; exit(1); } file://一一複製各條事件記錄 for(int i = 0; i < size; i++) callBackList[i] = cb.callBackList[i]; } CallBack::~CallBack(void) { delete [] callBackList; } void CallBack::AddCallBack(char *event, CallBackFunction pCBF, CallBack *pCBO) { file://如事件名爲空,退出 if( (event == NULL)?1:(strlen(event) == 0)) return; file://尋找因刪除事件記錄而產生的第一個空閒位置,並填寫新事件記錄 for(int start=0;start<lastpos;start++) if(callBackList[start].IsEmpty()){ callBackList[start] = EventRecord(event,pCBO,pCBF); break; } if(start < lastpos) return; file://確實存在空閒位置 file://沒有空閒位置,在回調錶後追加新記錄 if(lastpos == size) file://回調錶已滿,需“伸長” { EventRecord *tempList = callBackList;//暫存舊回調錶指針 file://以一定的步長“伸長”回調錶 callBackList = new EventRecord[size + CALLBACKLIST_INCREMENT]; if(!callBackList){ cerr<<"CallBack: memory allocation error."<<endl; exit(1); } file://複製舊回調錶中的記錄 for(int i = 0; i < size; i++) callBackList[i] = tempList[i]; delete [] tempList;//刪除舊回調錶 size += CALLBACKLIST_INCREMENT;//記下新回調錶的尺寸 } file://構造新的事件記錄並將其填入回調錶中 callBackList[lastpos] = EventRecord(event,pCBO,pCBF); lastpos++; } void CallBack::AddCallBack(char *event,CallBackStaticFunction pCBSF) { if( (event == NULL)?1:(strlen(event) == 0)) return; for(int start=0;start<lastpos;start++) if(callBackList[start].IsEmpty()){ callBackList[start] = EventRecord(event,pCBSF); break; } if(start < lastpos) return; file://a hole is found if(lastpos == size) file://event list is insufficient { EventRecord *tempList = callBackList; callBackList = new EventRecord[size + CALLBACKLIST_INCREMENT]; if(!callBackList){ cerr<<"CallBack: memory allocation error."<<endl; exit(1); } for(int i = 0; i < size; i++) callBackList[i] = tempList[i]; delete [] tempList; size += CALLBACKLIST_INCREMENT; } callBackList[lastpos] = EventRecord(event,pCBSF); lastpos++; } file://刪除註冊在指定事件上的成員函數 void CallBack::RemoveCallBack(char *event, CallBackFunction pCBF, CallBack *pCBO) { if( (event == NULL)?1:(strlen(event) == 0)) return; EventRecord er(event,pCBO,pCBF); for(int i = 0; i < lastpos; i++) if(callBackList[i] == er) callBackList[i].Flush(); } file://刪除註冊在指定事件上的靜態成員函數或普通函數 void CallBack::RemoveCallBack(char *event,CallBackStaticFunction pCBSF) { if( (event == NULL)?1:(strlen(event) == 0)) return; EventRecord er(event,pCBSF); for(int i = 0; i < lastpos; i++) if(callBackList[i] == er) callBackList[i].Flush(); } file://刪除註冊在指定事件上的所有回調函數 void CallBack::RemoveCallBack(char *event) { if( (event == NULL)?1:(strlen(event) == 0)) return; for(int i = 0; i < lastpos; i++) if(callBackList[i] == event) callBackList[i].Flush(); } void CallBack::CallCallBack(char *event, CallData callData) { if( (event == NULL)?1:(strlen(event) == 0)) return; CallBack *pCBO; CallBackFunction pCBF; CallBackStaticFunction pCBSF; MoveFirst(); while(!EndOfList()) { file://如當前事件記錄和指定事件不匹配,轉入下一條記錄繼續循環 if(!(callBackList[curpos] == event)) { MoveNext(); continue; } file://如找到匹配記錄 pCBO = callBackList[curpos].pointerToCBO; file://如事件記錄中回調對象指針爲空,說明該記錄中保存的是靜態函數指針 if(pCBO == NULL){ pCBSF = callBackList[curpos].pointerToCBSF; pCBSF(callData);//調用該靜態回調函數 } else file://如事件記錄中回調對象指針非空,說明該記錄中保存的是成員函數指針 { pCBF = callBackList[curpos].pointerToCBF; (pCBO->*pCBF)(callData);// 調用該回調對象的成員函數 } MoveNext(); } } |