1、当一个函数对象用作回调时,就是一个Command(命令)模式的实例。
2、回调函数就是一个通过函数指针调用的函数。
如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
3、回调是一种常见的编程技术,传统上被实现为一个指向函数的简单指针。
例如,考虑一个交互式按钮类型,它在屏幕上显示一个带标签的按钮,点击这个按钮,就会执行一个动作。
class Button {
public:
Button( const string &label ) : label_(label), action_( 0) { }
void setAction( void (newAction) ( ) ) { action_ = newAction; }
void onClick( ) const { if( action_ ) action_( ); }
private:
string label_;
void (* action_) ( );
//....
};
Button的用户设置回调函数,然后将Button移交给框架代码,后者可以侦测Button何时被点击了,并执行指定的动作。
extern void playMusic( );
//....
Button *b = new Button( "Anoko no namewa" );
b->setAction( playMusic );
registerButtonWithFramework ( b);
这种责任的分割通常被称为“好莱坞法则”,即“不要call我们,我们会call你“。
将按钮设置为执行正确的动作(如果它被点击了),而框架代码则知道,如果按钮被点击了就去调用该动作。
4、然而使用一个简单的函数指针作为回调的做法具有一些严格的限制。
函数往往需要一些数据才能工作,但一个函数指针没有相关联的数据。在上面的例子中,函数playMusic如何知道播放什么歌曲呢?
此时最好的办法是使用函数对象代替函数指针。将一个函数对象(通常为函数对象层次结构)与”好莱坞法则“结合使用,即为Command模式的一个实例。
使用这种面向对象方式很明显的一个好处是,函数对象可以封装数据。
另一个好处是函数对象可以通过虚拟成员表现出动态行为。换句话说,可以拥有一个相关的函数对象的层次结构。
首先使用Command模式重新设计Button类:
class Action { //Command
public:
virtual ~Action( );
virtual void operator( ) ( ) = 0;
virtual Action *clone( ) const = 0; //原型Prototype
};
class Button {
public:
Button ( const std::string &label ) : label_(label), action_(0) { }
void setAction( const Action *newAction ) {
Action *temp = newAction->clone( );
delete action_;
action_ = temp;
}
void onClick( ) const { if( action_ ) (*action_) ( ); }
private:
std::string label_;
Action *action_; //Command
//.....
};
现在,Button可以和任何”是一个“Action的函数对象协作,比如:
class PlayMusic : public Action {
public:
PlayMusic( const string &songFile ) : song_(songFile ) { }
void operator( ) ( ); //播放歌曲
private:
MP3 song_;
};
被封装的数据(此例为待播放的歌曲)既保持了PlayMusic函数对象的灵活性,也保持了它的安全性。
Button *b = new Button(" Anoko no namewa");
auto_ptr<PlayMusic> song( new PlayMusic("Anoko no namewa" ) );
b->setAction( song.get( ) );
5、第三个好处是可以处理类层次结构而不用去处理较为原始的、缺乏灵活性的结构(如函数指针)。