View Full Version: Patterns

C++ Learning Community > C++ Tips > Patterns


Title: Patterns
Description: The Command Pattern.


myork - December 3, 2005 05:43 PM (GMT)

Another leasson in patterns :-)
OK. If you have any comments don't add to this thread mail them to me first and I will add clarification for all. That way we can keep the tip clean.


There has been a lot of new people posting their 'calculator program' which is not bad. But they are all programming in C and we are trying to encourage C++. So here I will try and reimplement the 'calculator program using the command pattern. Hopefully I will be able to show that using the command pattern will make the code simpler and easier to maintain andor expand.

myork - December 3, 2005 05:44 PM (GMT)

Part 1:
=====

So what is the command pattern.
It is one method to easily replace the bug prone switch statment. Beginners like to think of the switch statment as a good multy way if statment. Simplistically yes, But in switch is actually a way of doing different things based on a type value.

Now the last part of the statment should start ringing some C++ bells about incapsulation and types.

CODE
void doThingActionOnObject(Object x)
{
   switch(x.getType())
   {
       case A:  doAThing(x);break;
       case B:  doBThing(x);break;
       case C:  doCThing(x);break;
       default:
           doError();
   }
}


So you are thinkign what is so hard about that.
Well nothing really. The problem happens when you start adding new types. What happens when there is more than (A,B,C). You will find that your code starts sprouting switch statments on the type all over the place to handle different situations.

A simple (slightly contrived [but not that much]) example.
======================================
Say Object is a Window, and you can have different types of window
Now we have to repeat the above switch code for every type of action that can de done on a Window. So we would need the following functions:
CODE
void doResizeOnWindow(Window w);
void doOpenOnWindow(Window w);
void doCloseOnWindow(Window w);
void doMoveOnWindow(Window w);etc..


Now you add a new type of Window. You have to search through your code for all the different actions that can happen based on Window type and add the appropriate code in multiple locations. This is where the command pattern springs into life.

The Command Pattern:
===============
The command pattern has basically one command (execute).

CODE
class ThingAction
{
   public:
       virtual void doThingAction(Object x) = 0;
};

class AAction: public ThingAction
{
   public:
       virtual void doThingAction(Object x)  {/* Do somthing with x */}
};
class BAction: public ThingAction
{
   public:
       virtual void doThingAction(Object x)  {/* Do somthing with x */}
};
class CAction: public ThingAction
{
   public:
       virtual void doThingAction(Object x)  {/* Do somthing with x */}
};


OK. So how do we use it.
Well when you create your object X you no longer give a type you give it a ThingAction object in its constructor. Now we can re-write void doThingActionOnObject(Object x).

CODE
void void doThingActionOnObject(Object x)
{
   x.getAction().execute(x); // Much simpler.
}


or we could make it even better,

Rather than getting the action object why not just ask the object to perform the action on itself.

CODE
void doThingActionOnObject(Object x)
{
   x->doThingAction();
}

void Object::doThingAction()
{
   getAction().execute(*this);
}



myork - December 3, 2005 11:28 PM (GMT)
Part 2
=========

OK, so how does this apply to the Calculator program?

Well basically everybody has been implementing the calculator like this:

CODE
int doMaths(char x,int lhs,int rhs)
{
   int result;
   switch(x)
   {
       case '+': result = doAdd(lhs,rhs);break;
       case '-': result = doSub(lhs,rhs);break;
       case '*': result = doMul(lhs,rhs);break;
       case '/': result = doDiv(lhs,rhs);break;
       default:
         Error();
   }
   return(result);
}


I think this design would be better implemented using the command pattern. Which would look something like this:

CODE
int doMaths(char x,int lhs,int rhs)
{
   int result;
   CalcCommand* command = commands[x];
   if (command != NULL)
   {   result = command->execute(lhs,rhs);
   }
   else
   {   Error();
   }
   return(result);
}


But you dont see how that helps.
OK. So lets actually build the commands.

CODE


#include <map>
#include <iostream>

class CalcCommand
{    public: virtual int execute(int lhs,int rhs) = 0;};

class doAdd: public CalcCommand
{    public: virtual int execute(int lhs,int rhs) {return(lhs + rhs);}};

class doSub: public CalcCommand
{    public: virtual int execute(int lhs,int rhs) {return(lhs - rhs);}};

class doMul: public CalcCommand
{    public: virtual int execute(int lhs,int rhs) {return(lhs * rhs);}};

class doDiv: public CalcCommand
{    public: virtual int execute(int lhs,int rhs) {return(lhs / rhs);}};

std::map<char,CalcCommand*>   commands;

int doMaths(char x,int lhs,int rhs);
int main(int argc,char* argv[])
{
   commands['+'] = new doAdd;
   commands['-'] = new doSub;
   commands['*'] = new doMul;
   commands['/'] = new doDiv;

   while(true)
   {
       std::cout << "Input Expression in the form 5 + 6 <enter>" << std::endl;

       int lhs;
       int rhs;
       char command;
       std::cin >> lhs >> command >> rhs;

       std::cout << "Result: " << doMaths(command,lhs,rhs) << std::endl;
   }
   /*
    * As the commands are dynamically created with new.
    * They need to be deleted.
    * The obvious way is just to delete each of the commands added above.
    * This is a bit error prone as when you add a command you must remember to delete it.
    * A better solution is to make the commands structure somthing that
    * automitcally destroys its content when going out of scope.
    * This is left as an exercise.
    */
   return(0);
}

void Error() {/*Do Error stuff */}
int doMaths(char x,int lhs,int rhs)
{
   int result;
   CalcCommand* command = commands[x];
   if (command != NULL)
   {   result = command->execute(lhs,rhs);
   }
   else
   {   Error();
   }
   return(result);
}




Hosted for free by InvisionFree