您的位置:首页 > 编程语言 > C语言/C++

在C++中模拟委托事件的方法(五)

2011-11-22 13:00 393 查看
五、事件链模拟事件

对应的例子工程名DelegateChainEvent

类模板的方法已经可以比较方便的让不同的对象接收同一个事件触发类的不同事件了,大多数的开发需要都能满足了,不过如果用过C#的人就会看到其中的一些问题,就是事件链的问题,因为C#允许对事件进行”+=”和”-=”的操作,即事件触发类的一个事件是一个事件链,可以挂接多个事件处理方法,当事件触发类这一个事件触发时,可以通知多个事件处理方法来处理事件,这种事件链的好处是,针对一个事件触发类(服务类)触发一个事件时,可以通知多个事件接收类都进行处理,比如界面上一个进度条图形显示进度,一个标签显示工作进度的具体文字信息,这样可以让进度条对象和标签对象同时接收一个工作类的progress事件,这个progress事件是一个事件链,当有了新的进度变化时,就调用progress事件链上的每个事件处理程序,进度条对象和标签对象都可以得到通知,各自更新自己的进度显示。

在以上3种比较可行的模拟事件方法中,事件处理函数的声明可以是任意的,可以有返回值,可以传递指针类型的形参或者引用类型的形参,即事件处理函数可以返回值,正如C#一样,如果采用事件链的方式,由于事件链中的函数被一个个调用时如果允许返回值(包括通过形参来返回),调用事件链中的下一个事件处理函数时不能确定怎么处理返回值,所以如果模拟事件链,就要求所有的事件处理函数的声明不能返回任何值(包括不能通过形参返回值)。

1、 具体的实现方法

(1)、委托模板类的定义与实现

#include <vector>
#include <utility>
#include <algorithm>

using std::vector;
using std::pair;

namespace dpex
{
template <class F>
class CDelegateChain
{
public:
CDelegateChain(void)
{
}

~CDelegateChain(void)
{
m_vDelegates.clear();
}

public:
bool RegisterEvent(F func, void *pParameter)
{
bool bRet = false;
pair<F, void *> pfvPair(func, pParameter);
vector<pair<F, void *>>::iterator result;

result = find(m_vDelegates.begin(), m_vDelegates.end(), pfvPair);
if (result == m_vDelegates.end())	//同一个对象的同一事件不允许多次接入
{
m_vDelegates.push_back(pfvPair);
bRet = true;
}
return bRet;
}

void UnRegisterEvent(F func, void *pParameter)
{
pair<F, void *> pfvPair(func, pParameter);
vector<pair<F, void *>>::iterator result;

result = find(m_vDelegates.begin(), m_vDelegates.end(), pfvPair);
if (result != m_vDelegates.end())
m_vDelegates.erase(result);
}

int GetEventAndParam(F *pFunc, void **ppParameter)
{
size_t len = m_vDelegates.size();
pair<F, void *> pfvPair;
if (len > 0)
{
pfvPair = m_vDelegates[len - 1];	//后入链的先调用
*pFunc = pfvPair.first;
*ppParameter = pfvPair.second;
return (int)len - 1;
}
else
return -1;		//返回-1表示没有后续的成员了
}
int GetNextEventAndParam(int iHandle, F *pFunc, void **ppParameter)
{
size_t len = m_vDelegates.size();
if ((iHandle > (int)len - 1) || (iHandle <= 0))
return -1;

pair<F, void *> pfvPair;
pfvPair = m_vDelegates[iHandle -1];
*pFunc = pfvPair.first;
*ppParameter = pfvPair.second;
return iHandle -1;
}

private:
vector<pair<F, void *>> m_vDelegates;
};
}

委托模板类CDelegateChain使用指向函数的指针作为模板参数,在类中使用一个列表保存这个函数指针参数和接收这个事件的参数,其定义如下:

vector<pair<F, void *>> m_vDelegates;

同时提供注册事件方法RegisterEvent,把要注册的事件处理函数和要传递的参数组成一个pair,然后进入m_vDelegates列表保存,使用事件处理函数作为参数的UnRegisterEvent方法,来取消事件注册,还提供GetEventAndParam和GetNextEventAndParam方法,事件触发类通过调用这2个方法来查询出事件链中每一个事件处理函数和需要传递的参数。

读者有兴趣可以自己实现对于"+="和"-="操作符的重载,在使用中就可以更像C#的事件方式了。

(2)、事件触发对象类CNotifyClass的类定义如下:

#include "../Delegate/DelegateChain.h"

using dpex::CDelegateChain;

typedef void (*PEVENT_PARAM_NORETURN)(void *, int);

class CNotifyClass
{
public:
CNotifyClass(void);
~CNotifyClass(void);

public:
void DoNotifyEventWork(int iArg);

public:
CDelegateChain<PEVENT_PARAM_NORETURN> m_pParam_NoReturn_EventHandler;
};

类实现如下:

#include "NotifyClass.h"

CNotifyClass::CNotifyClass(void)
{
}

CNotifyClass::~CNotifyClass(void)
{
}

void
CNotifyClass::DoNotifyEventWork(int iArg)
{
PEVENT_PARAM_NORETURN func;
void *pParameter;

int iHandle = m_pParam_NoReturn_EventHandler.GetEventAndParam(&func, &pParameter);
if ((iHandle >= 0) && (NULL != func))
func(pParameter, iArg);

while (iHandle >= 0)
{
iHandle = m_pParam_NoReturn_EventHandler.GetNextEventAndParam(iHandle, &func, &pParameter);
if ((iHandle >= 0) && (NULL != func))
func(pParameter, iArg);
}
}

事件触发类需要定义事件处理函数的声明,并为每一个事件定义委托模板类CDelegateChain类型的成员变量,代码类似如下:

CDelegateChain<PEVENT_PARAM_NORETURN> m_pParam_NoReturn_EventHandler;

需要触发事件时使用CDelegateChain类的GetEventAndParam和GetNextEventAndParam方法得到事件处理函数及要传递的参数,然后调用事件处理函数,类似如下的代码调用

PEVENT_PARAM_NORETURN func;
void *pParameter;

int iHandle = m_pParam_NoReturn_EventHandler.GetEventAndParam(&func, &pParameter);
if ((iHandle >= 0) && (NULL != func))
func(pParameter, iArg);

while (iHandle >= 0)
{
iHandle = m_pParam_NoReturn_EventHandler.GetNextEventAndParam(iHandle, &func, &pParameter);
if ((iHandle >= 0) && (NULL != func))
func(pParameter, iArg);
}

(3)、事件接收对象类或事件处理对象类CRecvEventClassOne的类定义如下:

#include "NotifyClass.h"

class CRecvEventClassOne
{
public:
CRecvEventClassOne(CNotifyClass *pncNotify);
~CRecvEventClassOne(void);

public:
void DoWork(int iArg);

protected:
static void OnParamNoReturnEvent(void *pvParam, int iArg);

protected:
CNotifyClass *m_pncNotify;
};

类实现如下:

#include "RecvEventClassOne.h"

CRecvEventClassOne::CRecvEventClassOne(CNotifyClass *pncNotify)
{
if (NULL == pncNotify)
return;
m_pncNotify = pncNotify;
m_pncNotify->m_pParam_NoReturn_EventHandler.RegisterEvent(OnParamNoReturnEvent, this);
}

CRecvEventClassOne::~CRecvEventClassOne(void)
{
if (NULL == m_pncNotify)
return;
m_pncNotify->m_pParam_NoReturn_EventHandler.UnRegisterEvent(OnParamNoReturnEvent, this);
m_pncNotify = NULL;
}

void
CRecvEventClassOne::DoWork(int iArg)
{
_tprintf(_T("CRecvEventClassOne Init Argument is %d\n"), iArg);
m_pncNotify->DoNotifyEventWork(iArg);
}

void
CRecvEventClassOne::OnParamNoReturnEvent(void *pvParam, int iArg)
{
_tprintf(_T("Run CRecvEventClassOne::OnParamNoReturnEvent, Argument is: %d\n"), iArg);
//if (pvParam != NULL)
//{
//	CRecvEventClassOne *p = reinterpret_cast<CRecvEventClassOne *>(pvParam);
//	//Do something
//}
}

事件接收类要定义满足事件接收函数声明格式的静态成员方法来接收事件,在注册事件时使用类似如下的方法来注册

m_pncNotify->m_pParam_NoReturn_EventHandler.RegisterEvent(OnParamNoReturnEvent, this);

m_pncNotify是事件触发类CNotifyClass类实例指针,通过它的成员变量m_pParam_NoReturn_EventHandler的方法来注册,同样使用类似如下代码来取消注册事件

m_pncNotify->m_pParam_NoReturn_EventHandler.UnRegisterEvent(OnParamNoReturnEvent, this);

这样就可以实现事件的挂接入链,接收事件,然后进行一定的处理了。

当有多个事件接收处理对象对同一个事件链挂接时,由于委托类CDelegateChain的方法GetEventAndParam和GetNextEventAndParam采用先进后出的方法,就是后接入的事件处理函数先被调用,要修改这个调用顺序读者可以自己修改。

(4)、使用的例子及输出

int _tmain(int argc, _TCHAR* argv[])
{
CNotifyClass ncNotify;
CRecvEventClassOne rec1(&ncNotify);
CRecvEventClassTwo rec21(&ncNotify, 1);
CRecvEventClassTwo rec22(&ncNotify, 2);
CRecvEventClassThree rec3(&ncNotify);
int iIn;

iIn = 10;
_tprintf(_T("DelegateChainEvent test, Init:%d\n"), iIn);
_tprintf(_T("DelegateChainEvent test, four object receive event\n"));
rec1.DoWork(iIn);
_tprintf(_T("DelegateChainEvent test, third object dont receive event\n"));
rec22.UnRecvEvent();
rec1.DoWork(iIn);

TCHAR c;
_tscanf(_T("%c"), &c);
return 0;
}

输出结果为:

DelegateChainEvent test, Init:10
DelegateChainEvent test, four object receive event
CRecvEventClassOne Init Argument is 10
Run CRecvEventClassThree::OnParamNoReturnEvent, Argument is: 10
Run CRecvEventClassTwo::OnParamNoReturnEvent, Argument is: 10
Run CRecvEventClassTwo::OnParamNoReturnEvent, m_iNum is: 2
Run CRecvEventClassTwo::OnParamNoReturnEvent, Argument is: 10
Run CRecvEventClassTwo::OnParamNoReturnEvent, m_iNum is: 1
Run CRecvEventClassOne::OnParamNoReturnEvent, Argument is: 10
DelegateChainEvent test, third object dont receive event
CRecvEventClassOne Init Argument is 10
Run CRecvEventClassThree::OnParamNoReturnEvent, Argument is: 10
Run CRecvEventClassTwo::OnParamNoReturnEvent, Argument is: 10
Run CRecvEventClassTwo::OnParamNoReturnEvent, m_iNum is: 1
Run CRecvEventClassOne::OnParamNoReturnEvent, Argument is: 10

程序中接入到事件触发类的事件接收类的对象顺序为CRecvEventClassOne对象、内部实例变量为1的CRecvEventClassTwo对象、内部实例变量为2的CRecvEventClassTwo对象、CRecvEventClassThree对象,事件调用的顺序正好反过来调用,调用顺序变成CRecvEventClassThree对象的事件、内部实例变量为2的CRecvEventClassTwo对象的事件、内部实例变量为1的CRecvEventClassTwo对象的事件、CRecvEventClassOne对象的事件,后面让内部实例变量为2的CRecvEventClassTwo对象不再接收这个事件,也就是从事件链中反注册不接收事件,再次执行工作,发现按照顺序CRecvEventClassThree对象的事件、内部实例变量为1的CRecvEventClassTwo对象的事件、CRecvEventClassOne对象的事件接到了事件通知。

2、 实现的要点

(1)、委托类的实现要点

a、 使用模板类的方法定义类,同时把要定义成事件的函数声明作为模板参数

b、 定义事件函数和要传递参数的pair作为元素的列表对象为成员变量

c、 定义注册与反注册事件的方法,支持事件的挂接与取消,方便事件接收类调用来注册事件

d、 定义方法来获取列表对象中每一个事件处理函数和要传递的参数,方便事件触发类调用来获取

这个委托模板类是一次性的,定义完成可以多次复用

(2)、事件触发类的实现要点

a、 事件触发类必须要定义要处理事件的函数声明

b、 事件触发类要使用事件函数声明作为模板参数定义委托类CDelegateChain的成员变量

CDelegateChain<PEVENT_PARAM_NORETURN> m_pParam_NoReturn_EventHandler;

a、 在工作时,需要触发事件的地方,通过使用CDelegateChain的方法获取每一个事件函数指针和需要传递的参数,然后循环使用传递参数调用事件函数

(3)、事件接收对象类或事件处理对象类的实现要点

a、 使用静态成员方法定义与要接收的事件函数声明相同的方法

b、 使用事件触发类响应事件的委托类成员变量的RegisterEvent和UnRegisterEvent方法注册与反注册是否接收事件,在注册事件时根据需要传递自身this来作为参数

c、 在事件处理的静态方法中,根据需要转换参数为当前对象,然后进行工作

3、 优缺点

(1)、优点

a、可以根据需要选择需要接收的事件

b、事件处理方法不需要必须是public的方法,任意访问类别都可以

c、可以让不同的对象接收同一个事件触发类(服务类)的不同事件

d、委托模板类一次性开发后,直接使用,在事件触发类和事件接收类中的代码简洁,代码量少,容易理解,不容易出错。

e、可以让多个事件接收对象接收同一个事件触发类(服务类)的同一个事件

(2)、缺点

a、针对事件接收类对象的指针参数仍然被转化为void *,相应的其类型安全性相对差些,但是对于事件处理函数的指针因为是模板类参数,所以安全性没有问题,可以在编译期间被检查

b、这里的事件处理函数的声明类型,不能带有返回值,包括通过形参来返回值的情况
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: