Commands and Hollywood



Item 19. Commands and Hollywood

When a function object is used as a callback, that's an instance of the Command pattern.

What's a callback? Suppose you're taking a long trip, and I lend you my car for the purpose. Given the condition of my car, I'll probably also hand you a sealed envelope with a telephone number in it, along with instructions to call the number in the envelope if you experience any engine problems. That's a callback. You do not have to know the number in advance (it may be the number of a good repair shop, a bus line, or the city dump), and in fact you may never have to call the number. In effect, the task of handling the "engine trouble" event has been partitioned between you (also known as the "framework") and me (also known as the "client of the framework"). You know when it's time to do something but not what to do. I know what to do if a particular event occurs but not when to do it. Together we make a complete application.

Callbacks are a common programming technique and have traditionally been implemented as simple pointers to functions (see Function Pointers [14, 49]). For example, consider an interactive button type that displays a labeled button on the screen and executes an action if it's clicked.

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_)();                                              
    //...                                                             
};                                                                    

A user of a Button would set a callback function and then hand the Button over to framework code that could detect when the Button is clicked and execute the action.

extern void playMusic();
//...
Button *b = new Button( "Anoko no namaewa" );
b->setAction( playMusic );
registerButtonWithFramework( b );

This partitioning of responsibility is often called the "Hollywood Principle," as in "Don't call us; we'll call you." We set up the button to perform the correct action if it should ever be clicked, and the framework code knows to invoke that action if the button is clicked.

However, using a simple function pointer as a callback has severe limitations. Functions often need data with which to work, but a function pointer has no associated data. In the example above, how does the playMusic function know what song to play? The usual quick fix is either to severely limit the scope of the function

extern void playAnokoNoNamaewa();                                       
//...                                                                   
b->setAction( playAnokoNoNamaewa );                                     

or to resort to disreputable and dangerous coding practices, such as the use of a global variable:

extern const MP3 *theCurrentSong = 0;                                  
//...                                                                  
const MP3 anokoNoNamaewa ( "AnokoNoNamaewa.mp3" );                     
theCurrentSong = &anokoNoNamaewa;                                      
b->setAction( playMusic );                                             

A better approach is typically to use a function object rather than a function pointer. Use of a function objector more typically a function object hierarchyin conjunction with the Hollywood Principle is an instance of the Command pattern.

One obvious benefit of the object-oriented approach is that a function object can have encapsulated data. Another advantage is that a function object can have dynamic behavior though virtual members; that is, we can have a hierarchy of related function objects (see Function Objects [18, 63]). We gain a third advantage as well, but we'll get to that later. First let's redesign our Button to employ the Command pattern:

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
    //...
};

A Button can now work with any function object that is-a Action, like this one:

class PlayMusic : public Action {
  public:
    PlayMusic( const string &songFile )
        : song_(song) {}
    void operator ()(); // plays the song
  private:
    MP3 song_;
};

The encapsulated data (in this case, the song to play) preserves both the flexibility and safety of the PlayMusic function object.

Button *b = new Button( "Anoko no namaewa" );
auto_ptr<PlayMusic>
    song( new PlayMusic( "AnokoNoNamaewa.mp3" ) );
b->setAction( song );

So what's the mysterious third advantage of Command to which we referred earlier? Simply that it's advantageous to work with a class hierarchy rather than a more primitive, less flexible structure like a function pointer. Because of the presence of a Command hierarchy, we've already been able to compose the Prototype pattern with Command in order to produce clonable commands (see Virtual Constructors and Prototype [29, 99]). We can continue in this vein and compose additional patterns with Command and Prototype for additional flexibility.