C++必知必会之(19)Command模式与好莱坞法则

来源:互联网 发布:程序界面设计软件 编辑:程序博客网 时间:2024/05/29 11:11

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、第三个好处是可以处理类层次结构而不用去处理较为原始的、缺乏灵活性的结构(如函数指针)。


原创粉丝点击