用过C++进行过&&keyword=%C3%E6%CF%F2%B6%D4%CF%F3">面向对象程序设计的用户都知道,程序中的对象很少单独存在。不考虑对象间的相互作用几乎是不可能的。所以,标识对象间的关系或建立对象间的消息连接是&&keyword=%C3%E6%CF%F2%B6%D4%CF%F3">面向对象程序设计的一项重要任务。本文着重从C++程序设计的角度,提出一种建立对象间消息连接的实用方法。如果你想详细了解&&keyword=%C3%E6%CF%F2%B6%D4%CF%F3">面向对象程序设计技术,请参阅有关专著。
大家都知道对象是数据和方法的封装体。在C++中,它们分别表现为数据成员和成员&&函数。程序设计者通过执行对象的各种方法,来改变对象的状态(即改变对象的&&属性数据)。从而使该对象发生某些“&&事件”。当一对象发生某&&事件时,它通常需向其它相关对象发送“消息”,请求它们作出一些处理。 这时,发生&&事件并向其它对象请求处理的对象被称为“&&事件对象”,而处理&&事件的对象被称为“回调对象”。回调对象对&&事件的处理称为“回调&&函数”。
在C++中,这一过程相当于:当&&事件对象发生&&事件时,调用回调对象的某些成员&&函数。通常的作法是回调对象向&&事件对象传递对象&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针。但这种方法不通用。为了减少程序设计的工作量,本文提出一种建立对象间消息连接的系统方法。它的思路是:将“&&事件发生→请求处理→执行处理”这一过程抽象成一个“回调”(CallBack)类。通过继承,用户可以轻松获取建立对象间消息连接的机制。
一、回调类的数据结构及其成员&&函数
本文提出的CallBack类支持三种回调&&函数。它们是:回调对象中的成员&&函数,属于回调类的静态成员&&函数和普通的C&&函数。CallBackle类中包含一回调&&函数表callBackList。它用于记录&&事件名称,指向回调&&函数及回调对象的&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针。该表的每一个节点为一个&&事件记录EventRecord。每个&&事件记录包含三个域:&&事件名&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针eventName,指向回调对象的&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针pointerToCBO,指向回调&&函数的&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针pointerToCBF或pointerToCBSF(其中,pointerToCBF指向回调对象的成员&&函数,pointerToCBSF指向回调类的静态成员&&函数或普通&&函数。它们同处于一共用体内)。CallBack类所提供的回调机制是这样的:在&&事件对象上注册回调对象中的回调&&函数;当&&事件发生时,&&事件对象在其回调表中检索并执行回调&&函数。从而使二者的消息连接得以建立。(关于该类的具体实现,请参阅文后所附的程序清单) 回调对象
&&事件对象 &&事件名 回调对象&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针 回调&&函数&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针
“event” pointerCBO pointerToCBF或
pointerTOCBSF
- - - - - - |
AddCallBack: 注册&&事件名和指向回调&&函数,回调对象的&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针
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是指向&&事件名字符串的&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针,p是指向回调对象的&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针,cbf和cbsf分别是指向成员&&函数及静态成员&&函数(或普通&&函数)的&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针。当回调&&函数来自某回调对象SomeObject时,传递成员&&函数&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针应采用如下格式:
(CallBackFunction)&SomeObject::MemberFunctionName; 传递SomeObject类的某静态成员&&函数&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针应采用格式: (CallBackStaticFunction)& SomeObject::FunctionName;传递程序中普通&&函数&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针时,只需传递&&函数名即可。 |
CallBack类的成员&&函数void CallBack::CallCallBack(char *ename, CallData calldata = NULL)用来调用注册在&&事件ename上的所有回调&&函数。其中,calldata为数据&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针(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.为&&事件对象注册回调数据。包括:&&事件名,回调&&函数名,指向回调对象的&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针。
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类,在对象间建立消息连接已变为一项很简单和优美的工作。
附:程序清单(本程序在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;//回调数据&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针类型定义
typedef void (CallBack::*CallBackFunction)(CallData); file://指向回调成员&&函数的&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针
typedef void (*CallBackStaticFunction)(CallData); file://指向静态成员&&函数或普通&&函数的&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针类型定义
class EventRecord{ private: char *eventName; file://回调&&事件名称 CallBack *pointerToCBO;//指向回调对象的&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针 file://指向成员&&函数的&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针和指向静态成员&&函数(或普通&&函数)&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针的共用体 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;//暂存旧回调表&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针 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://如&&事件记录中回调对象&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针为空,说明该记录中保存的是静态&&函数&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针 if(pCBO == NULL){ pCBSF = callBackList[curpos].pointerToCBSF; pCBSF(callData);//调用该静态回调&&函数 } else file://如&&事件记录中回调对象&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针非空,说明该记录中保存的是成员&&函数&&keyword=%D6%B8%D5%EB&Submit=+%CB%D1%CB%F7+">指针 { pCBF = callBackList[curpos].pointerToCBF; (pCBO->*pCBF)(callData);// 调用该回调对象的成员&&函数 } MoveNext(); } } |