BlackWaspTM

This web site uses cookies. By using the site you accept the cookie policy.This message is for compliance with the UK ICO law.

Design Patterns
.NET 2.0+

Command Design Pattern

The command pattern is a design pattern that enables all of the information for a request to be contained within a single object. The command can then be invoked as required, often as part of a batch of queued commands with rollback capabilities.

Example Command

In the opening section of the article an example of the command design pattern was described. In the example the pattern was used to control the actions of a robot. This robot would be remotely controlled, as it would be on the surface of another planet.

In the example, the commands dictate the movement of the robot and the use of a scoop to sample the planet's surface. The receiver would be the robot itself and the invoker would be a timed system running within the robot's software. The client would be controlled by the users on Earth who could create a queue of commands. The robot would also include an undo function to reverse a number commands as required.

In this section of the article we will create a simplified version of the classes that could be used to control such a robot. The first class will be the abstract base class for robot commands. This is slightly different to CommandBase in the earlier code as it includes an Undo method as well as an Execute method.

public abstract class RobotCommand
{
    protected Robot _robot;

    public RobotCommand(Robot robot)
    {
        _robot = robot;
    }

    public abstract void Execute();

    public abstract void Undo();
}

We can now create the three commands that the robot will be able to perform. The first command will allow the robot to be moved forwards by a specified number of millimetres. If the value given is negative, the robot will move backwards. A similar command will allow the robot to be rotated a number of degrees to the left, or to the right when the value is negative. The third command moves the sample-gathering scoop upwards or downwards. In each command, the Execute and Undo methods are implemented. The Undo method is the reverse operation to Execute.

public class MoveCommand : RobotCommand
{
    public int ForwardDistance { get; set; }

    public MoveCommand(Robot robot) : base(robot) { }

    public override void Execute()
    {
        _robot.Move(ForwardDistance);
    }

    public override void Undo()
    {
        _robot.Move(-ForwardDistance);
    }
}


public class RotateCommand : RobotCommand
{
    public double LeftRotation { get; set; }

    public RotateCommand(Robot robot) : base(robot) { }

    public override void Execute()
    {
        _robot.RotateLeft(LeftRotation);
    }

    public override void Undo()
    {
        _robot.RotateLeft(-LeftRotation);
    }
}


public class ScoopCommand : RobotCommand
{
    public bool ScoopUpwards { get; set; }

    public ScoopCommand(Robot robot) : base(robot) { }

    public override void Execute()
    {
        _robot.Scoop(ScoopUpwards);
    }

    public override void Undo()
    {
        _robot.Scoop(!ScoopUpwards);
    }
}

With the commands in place we can create the functionality that supports them. This will be held within a single receiver class that named "Robot". Rather than actually track the progress of a simulated robot, each method outputs some text to the console to explain the action that would have occurred in the real system.

public class Robot
{
    public void Move(int forwardDistance)
    {
        if (forwardDistance > 0)
            Console.WriteLine("Robot moved forwards {0}mm.", forwardDistance);
        else
            Console.WriteLine("Robot moved backwards {0}mm.", -forwardDistance);
    }

    public void RotateLeft(double leftRotation)
    {
        if (leftRotation > 0)
            Console.WriteLine("Robot rotated left {0} degrees.", leftRotation);
        else
            Console.WriteLine("Robot rotated right {0} degrees.", -leftRotation);
    }

    public void Scoop(bool upwards)
    {
        if (upwards)
            Console.WriteLine("Robot gathered soil in scoop.");
        else
            Console.WriteLine("Robot released scoop contents.");
    }
}

To complete the key classes of the pattern we must now code the invoker. This will be an enhanced version of that in the template code for the pattern, as it will contain a queue of commands rather than a single reference. In addition, a stack of commands will be used to keep a record of the robot's activities. This stack can then be used to undo commands that were executed in error. The code for the invoker is as follows:

public class RobotController
{
    public Queue<RobotCommand> Commands;
    private Stack<RobotCommand> _undoStack;

    public RobotController()
    {
        Commands = new Queue<RobotCommand>();
        _undoStack = new Stack<RobotCommand>();
    }

    public void ExecuteCommands()
    {
        Console.WriteLine("EXECUTING COMMANDS.");

        while (Commands.Count > 0)
        {
            RobotCommand command = Commands.Dequeue();
            command.Execute();
            _undoStack.Push(command);
        }
    }

    public void UndoCommands(int numUndos)
    {
        Console.WriteLine("REVERSING {0} COMMAND(S).", numUndos);

        while (numUndos > 0 && _undoStack.Count > 0)
        {
            RobotCommand command = _undoStack.Pop();
            command.Undo();
            numUndos--;
        }
    }
}
9 August 2009