Pages

boost::function and the command pattern

You know the command pattern, a very useful abstraction indeed. For instance we could use it to implement a fake tape recorder accordingly to this design:

We have a class, TapeRecorder, that implements the recorder itself, giving us access to its functionality.

Than we have a pure virtual base class, CommandBase, that would be base for each actual class implementing a recorder command, like PlayCommand, StopCommand, etc.

Given that, our GUI, or whatever interface we provide to the user, could access the commands on the recorder through the Command classes.

Here is an implementation of this schema:
#include <iostream>
#include <string>

using std::cout;
using std::endl;
using std::string;

class TapeRecorder
{
public:
  void play() { cout << "Since my baby left me..." << endl; }
  void stop() { cout << "OK, taking a break" << endl; }
  void forward() { cout << "whizzz" << endl; }
  void rewind() { cout << "zzzihw" << endl; }
  void record(const string& sound) { cout << "Recorded: " << sound << endl; }
};

class CommandBase
{
public:
  virtual bool enabled() const =0;
  virtual void execute() =0;

  virtual ~CommandBase() {}
};

class PlayCommand : public CommandBase
{
  TapeRecorder* p_;
public:
  PlayCommand(TapeRecorder* p) : p_(p) {}

  bool enabled() const { return true; }
  void execute() { p_->play(); }
};

class StopCommand : public CommandBase
{
  TapeRecorder* p_;
public:
  StopCommand(TapeRecorder* p) : p_(p) {}
  bool enabled() const { return true; }
  void execute() { p_->stop(); }
};

void commandPattern()
{
  cout << "Using the command pattern" << endl;

  TapeRecorder tr;

  CommandBase* pPlay = new PlayCommand(&tr);
  CommandBase* pStop = new StopCommand(&tr);

  cout << "Pressing button play" << endl;
  pPlay->execute();
  cout << "Pressing button stop" << endl;
  pStop->execute();

  delete pPlay;
  delete pStop;
}
Sometimes we don't have an alternative to this, especially if any command needs to be managed in its peculiar way, but in a case like this, having a specific class for each real command is just overkilling.

We can think of a generic Command class that would be initialized with the correct command, in this way:
class TapeRecorderCommand : public CommandBase
{
  void (TapeRecorder::*func_)(); 
  TapeRecorder* p_;
public:
  TapeRecorderCommand(TapeRecorder* p, void (TapeRecorder::*func)()) : p_(p), func_(func) {}

  bool enabled() const { return true; }
  void execute() { (p_->*func_)(); }
};
Now our user code would look in this way:
void commandPatternImproved()
{
  cout << endl << "Using the improved command" << endl;

  TapeRecorder tr;

  CommandBase* pPlay = new TapeRecorderCommand(&tr, &TapeRecorder::play);
  CommandBase* pStop = new TapeRecorderCommand(&tr, &TapeRecorder::stop);

  cout << "Pressing button play" << endl;
  pPlay->execute();
  cout << "Pressing button stop" << endl;
  pStop->execute();

  delete pPlay;
  delete pStop;
}
So, we just use the TapeRecorderCommand for all the commands, simplifying the hierarchy.

Well, at this point we could even get rid of all the hierarchy and just use a class that would store a boost::function, in this way:
class Command2
{
  boost::function<void()> f_;
public:
  Command2() {}
  Command2(boost::function<void()> f) : f_(f) {}

  void execute() { if(f_) f_(); }
  template <typename Func> void setFunction(Func f) { f_ = f; }
  bool enabled() const { return f_; }
};
Class that would be used in this way:
void commandPatternBoost()
{
  cout << endl << "Using boost::function and boost::bind" << endl;

  TapeRecorder tr;

  Command2 play(boost::bind(&TapeRecorder::play,&tr));
  Command2 stop(boost::bind(&TapeRecorder::stop,&tr));
  Command2 forward(boost::bind(&TapeRecorder::stop,&tr));
  Command2 rewind(boost::bind(&TapeRecorder::rewind,&tr));
  Command2 record;

  cout << "Pressing button play" << endl;
  if (play.enabled())
    play.execute();

  cout << "Pressing button stop" << endl;
  stop.execute();

  cout << "Pressing button record" << endl;
  string s = "What a beautiful morning...";
  record.setFunction(boost::bind(&TapeRecorder::record, &tr, s));
  record.execute();
}
Notice that we combine the usage of boost::function with boost::bind, since we want to use, as a function, the member function of a specific class object. It is a pleasure to see how smoothly they work together.

But do we really need the Command class to wrap the boost::function object? Actually no, we don't. All the functionality we require from that class are already available in the boost class. We could just use it, maybe making its usage a bit more explicit with a typedef:
void commandPatternBoost2()
{
  cout << endl << "Fully using boost::function" << endl;

  TapeRecorder tr;

  typedef boost::function<void()> BoostCommand;
  BoostCommand play(boost::bind(&TapeRecorder::play, &tr));
  BoostCommand stop(boost::bind(&TapeRecorder::stop, &tr));
  BoostCommand forward(boost::bind(&TapeRecorder::stop, &tr));
  BoostCommand rewind(boost::bind(&TapeRecorder::rewind, &tr));

  cout << "Pressing button play" << endl;
  play();

  cout << "Pressing button stop" << endl;
  stop();

  cout << "Pressing button record" << endl;
  string s = "What a beautiful morning...";
  BoostCommand record(boost::bind(&TapeRecorder::record, &tr, s));
  record();
}
Cool, isn't it?

And if you are using a compiler that already supports the C++0x TR1, you could use std::function and std::bind exactly in the same way:
void commandPatternCpp0x()
{
  cout << endl << "Using std::function" << endl;

  TapeRecorder tr;

  typedef std::function<void()> Command0x;

  Command0x play(std::bind(&TapeRecorder::play, &tr));
  Command0x stop(std::bind(&TapeRecorder::stop, &tr));
  Command0x forward(std::bind(&TapeRecorder::stop, &tr));
  Command0x rewind(std::bind(&TapeRecorder::rewind, &tr));

  cout << "Pressing button play" << endl;
  play();

  cout << "Pressing button stop" << endl;
  stop();

  cout << "Pressing button record" << endl;
  string s = "What a beautiful morning...";
  Command0x record(std::bind(&TapeRecorder::record, &tr, s));
  record();
}
The code is based on an example provided by "Beyond the C++ Standard Library: An Introduction to Boost", by Björn Karlsson, an Addison Wesley Professional book. An interesting reading indeed.

2 comments:

  1. Excellent Article. Thanks.

    ReplyDelete
    Replies
    1. I'm happy it has been useful. Thank you for your feedback.

      Delete