Level: Beginner + to Object Oriented Programming; Beginner +
with .Net and C#
The command pattern is a very useful pattern when you want
to give your users the ability to do certain things and undo them. Typical
examples are the undo and redo on many programs today. This functionality is
accomplished with the command pattern. The GOF book says to use a command
pattern when:
1. Parameterize objects by an action to perform
2. Specify, queue, and execute requests at different times
3. Support undo (GOF235)
The pattern encapsulates the commands of your application to
self contained classes. For our example, we will revisit the dog example I
have used before and play God for a while. Our operations will be to magically
increase and decrease our bulldog's age. Since we are almighty, we of course
can change our minds and undo our meddling.
First we start with a very simple Bulldog class.
Listing 18
public class CBulldog
{
private int _Age;
private string _BarkString;
public CBulldog()
{
_Age = 0;
_BarkString = "Woof";
}
public void Bark()
{
Console.WriteLine(_BarkString);
}
public int MyAge
{
get
{
return _Age;
}
set
{
_Age = value;
}
}
public string MyBark
{
get
{
return _BarkString;
}
set
{
_BarkString = value;
}
}
}
All the class does is to keep up with the dog’s age, provide
a method to change it and allows for the dog to bark. The bark method writes
the dog's bark out to the console window.
For our command classes, we first define three interfaces
then our main command classes. The interfaces allow different kinds of
commands to be acted upon as the same type of class. For our example, we will
use two different commands, one to change the dog’s age and another to change
how he barks.
First we create our interfaces:
Listing 19
public interface IBaseCommand
{
void Undo();
}
public interface IAgeCommand: IBaseCommand
{
void AddAge(int age);
}
public interface IBarkCommand: IBaseCommand
{
void ChangeBark(string Newbark);
}
We base the two main command interfaces of a generic one
that allows for an undo. This will allow us to iterate over all interfaces
later and undo them as a group. We can cast any IAgeCommand or IBarkCommand
down to an IBaseCommand.
Now for our two classes that encapsulate the commands. They
are pretty straight forward. Each takes a parameter of the dog to operate on
in the constructor and saves off any relevant information like the last age,
etc.
Listing 20
public class AgeCommand: IAgeCommand
{
private CBulldog _OurDog;
private int _OldAge;
public AgeCommand(CBulldog o)
{
_OurDog = o; //dog to work on
}
public void AddAge(int age)
{
_OldAge = _OurDog.MyAge;
_OurDog.MyAge = age;
}
public void Undo()
{
_OurDog.MyAge = _OldAge;
}
}
public class BarkCommand: IBarkCommand
{
private CBulldog _OurDog;
private string _OldBark;
public BarkCommand(CBulldog o)
{
_OurDog = o; //dog to work on
}
public void ChangeBark(string Newbark)
{
_OldBark = _OurDog.MyBark;
_OurDog.MyBark = Newbark;
}
public void Undo()
{
_OurDog.MyBark = _OldBark;
}
}
Our test of the code will be to create a dog, show its initial
settings, change them, show the new settings and then revert back to the
original. For simplicity, we store the list of used commands in an ArrayList
object.
Listing 21
CBulldog oDog;
ArrayList cmd = new ArrayList();
oDog = new CBulldog();
AgeCommand a1 = new AgeCommand(oDog);
Console.WriteLine(oDog.MyAge.ToString());
a1.AddAge(4);
cmd.Add(a1);
Console.WriteLine(oDog.MyAge.ToString());
BarkCommand b1 = new BarkCommand(oDog);
oDog.Bark();
b1.ChangeBark("Yip Yip");
cmd.Add(b1);
oDog.Bark();
//at this point we have a dog that as beenchanged
//we also have an array that has the command
//objects that were used for the change.
//To undo, just loop through the arraylistbackwards
cmd.Reverse();
foreach (IBaseCommand bse in cmd)
{
bse.Undo();
}
oDog.Bark();
Console.WriteLine(oDog.MyAge.ToString());