Callback是這樣的一類對(duì)象(在這里不能簡(jiǎn)單的理解為"回調(diào)函數(shù)"了):你注冊(cè)一個(gè)函數(shù),以及調(diào)用它時(shí)的參數(shù),希望在滿足某個(gè)條件時(shí),以這些注冊(cè)的函數(shù)調(diào)用這個(gè)回調(diào),完成指定的操作.
很多地方會(huì)使用到這個(gè)概念.比如,UI程序中,注冊(cè)一個(gè)函數(shù),當(dāng)某個(gè)鼠標(biāo)事件發(fā)生的時(shí)候自動(dòng)調(diào)用;比如,創(chuàng)建一個(gè)線程,線程開始運(yùn)行時(shí),執(zhí)行注冊(cè)的函數(shù)操作.
Callback的出現(xiàn),本質(zhì)上是因?yàn)楹芏嗖僮鞫加挟惒交男枰?--你不知道它什么時(shí)候會(huì)執(zhí)行,只需要告訴它,在執(zhí)行的時(shí)候,調(diào)用我告訴你的操作即可.
盡管使用的地方不盡相同,但是從程序的角度上看,做的事情都是差不多的.
要實(shí)現(xiàn)一個(gè)Callback,最大的難點(diǎn)在于,變化的參數(shù)和需要統(tǒng)一的對(duì)外接口之間的矛盾.也就是說,回調(diào)函數(shù)執(zhí)行時(shí)參數(shù)的數(shù)量是你無法預(yù)知的.而你需要對(duì)外提供一個(gè)統(tǒng)一的接口,調(diào)用該接口的不需要關(guān)注到注冊(cè)進(jìn)去的到底是什么,有幾個(gè)參數(shù),具體的執(zhí)行留到回調(diào)真正執(zhí)行的時(shí)候再去處理.
簡(jiǎn)單介紹一下目前我所知道的幾種方法,有C++的,也有C的.
1) 使用模板
將不同參數(shù)的類型,作為模板的參數(shù).比如:
#include <stdio.h>

class Closure


{
public:

virtual ~Closure()
{}

virtual void Run()
{}

protected:

Closure()
{}
};

template<class T>
class Callback0
: public Closure


{
public:
typedef void (T::*Done)();
public:
Callback0(T *obj, Done run)
: object_(obj)
, run_(run)

{
}

virtual void Run()

{
(object_->*run_)();
}
private:
T *object_;
Done run_;
};

template<class T, class T1>
class Callback1
: public Closure


{
public:
typedef void (T::*Done)(T1);
public:
Callback1(T *obj, Done run, T1 arg)
: object_(obj)
, run_(run)
, arg0_(arg)

{
}

virtual void Run()

{
(object_->*run_)(arg0_);
}
private:
T *object_;
Done run_;
T1 arg0_;
};

class Test


{
public:
void Run0()

{
printf("in Test::Run0\n");
}

void Run1(int i)

{
printf("in Test::Run1\n");
}
};

template<class T>
Closure*
NewCallback(T *obj, void (T::*member)())


{
return new Callback0<T>(obj, member);
}

template<class T, class T1>
Closure*
NewCallback(T *obj, void (T::*member)(T1), T1 P)


{
return new Callback1<T, T1>(obj, member, P);
}

int main()


{
Test test;

Closure *callback0 = NewCallback(&test, &Test::Run0);
callback0->Run();
delete callback0;

Closure *callback1 = NewCallback(&test, &Test::Run1, 1);
callback1->Run();
delete callback1;

return 0;
}

在這里,定義了一個(gè)虛擬基類Closure,它對(duì)外暴露一個(gè)接口Run,也就是,使用它的時(shí)候只需要使用Closure指針->Run即可以執(zhí)行注冊(cè)的操作.需要注意的是,Closure的構(gòu)造函數(shù)聲明為protected,也就是僅可以被子類調(diào)用.
接下來,定義的Closure'子類都是模板類,其中的模板都是參數(shù),我分別實(shí)現(xiàn)了兩種子類,分別是不帶參數(shù)的和帶一個(gè)參數(shù)的.將回調(diào)函數(shù)需要的參數(shù),保存在具體的子類對(duì)象中.
最后,對(duì)外構(gòu)造一個(gè)Closure指針時(shí),最好也提供一致的接口,這里分別為兩種子類實(shí)現(xiàn)了NewCallback函數(shù).
剩下的,理解起來應(yīng)該不難.
這種實(shí)現(xiàn)方法,看明白的就知道,其實(shí)難點(diǎn)不多.它將回調(diào)函數(shù)和傳遞給回調(diào)函數(shù)的參數(shù)放在了一個(gè)類中,當(dāng)外部調(diào)用Run接口的時(shí)候,再根據(jù)內(nèi)部的實(shí)現(xiàn)來具體進(jìn)行操作.
但是,我本人很不喜歡模板滿天飛的代碼,所以應(yīng)該還有些別的方法來實(shí)現(xiàn)吧?
2) 不使用模板,將參數(shù)和回調(diào)分離,分別對(duì)參數(shù)和回調(diào)進(jìn)行抽象
CEGUI是一款開源的游戲UI項(xiàng)目,早幾年我還在做著3D引擎程序員夢(mèng)的時(shí)候,曾經(jīng)看過一些,對(duì)它的一些代碼還有些印象.
里面對(duì)UI事件的處理,也使用了類似Callback的機(jī)制(這種使用場(chǎng)景最開始的時(shí)候曾經(jīng)說過,所以應(yīng)該不會(huì)感到意外).
在CEGUI中,一個(gè)事件由一個(gè)虛擬基類Event定義,處理事件的時(shí)候調(diào)用的是它的純虛函數(shù)fireEvent,而這個(gè)函數(shù)的參數(shù)之一是EventArgs--這又是一個(gè)虛擬基類.
所以,熟悉面向?qū)ο蟮娜?應(yīng)該可以很快的反應(yīng)過來了:在Event的子類中實(shí)現(xiàn)fireEvent,而不同的函數(shù)參數(shù),可以從EventArgs虛擬基類中派生出來.
于是,具體回調(diào)的時(shí)候,僅僅需要調(diào)用 Event類指針->fireEvent(EventArgs類指針)就可以了.
(我在這里對(duì)CEGUI的講解,省略了很多細(xì)節(jié),僅僅關(guān)注到最關(guān)注的點(diǎn),感興趣的可以自己去看看代碼)
對(duì)比1)和2)兩種解決方法,顯然對(duì)我這樣不喜歡模板的人來說,更喜歡2).除了模板的代碼讀起來比較頭大,以及模板會(huì)讓代碼量增大之外.喜歡2)的原因還在于,C對(duì)"類模板"機(jī)制的支持實(shí)在是欠缺,至今除了使用宏之外,似乎找不到很好的辦法能夠?qū)崿F(xiàn)類C++的模板機(jī)制.但是,如果采用2)的繼承接口的方式,C就可以很清楚的實(shí)現(xiàn)出來.所以就有了下面C的實(shí)現(xiàn):
3) C的實(shí)現(xiàn).
有了2)的準(zhǔn)備,使用C來實(shí)現(xiàn)一個(gè)類似的功能,應(yīng)該很容易了,下面貼代碼,應(yīng)該很清楚的:
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef struct event


{
void (*fireEvent)(void *arg);
void *arg;
}event_t;

typedef struct event_arg1


{
int value;
}event_arg1_t;

void fireEvent_arg1(void *arg)


{
event_arg1_t *arg1 = (event_arg1_t*)arg;

printf("arg 1 = %d\n", arg1->value);
}

#define NewEvent(event, eventtype, callback) \

do
{ \
*(event) = (event_t*)malloc(sizeof(event_t)); \
assert(*(event)); \
(*(event))->arg = (eventtype*)malloc(sizeof(char) * sizeof(eventtype)); \
assert((*(event))->arg); \
(*(event))->fireEvent = callback; \
} while (0)

#define DestroyEvent(event) \

do
{ \
free((*(event))->arg); \
free(*(event)); \
} while(0)

int main()


{
event_t *event;

NewEvent(&event, event_arg1_t, fireEvent_arg1);
((event_arg1_t*)(event->arg))->value = 100;
event->fireEvent(event->arg);

DestroyEvent(&event);

return 0;
}