The Command design pattern encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
C# code examples of the Command design pattern is provided in 3 forms:
A visualization of the classes and objects participating in this pattern.
The classes and objects participating in this pattern include:
Command
)
CalculatorCommand
)
CommandApp
)
User
)
Calculator
)
This structural code demonstrates the Command pattern which stores requests as objects allowing clients to execute or playback the requests.
using System;
namespace Command.Structural
{
/// <summary>
/// Command Design Pattern
/// </summary>
public class Program
{
public static void Main(string[] args)
{
// Create receiver, command, and invoker
Receiver receiver = new Receiver();
Command command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker();
// Set and execute command
invoker.SetCommand(command);
invoker.ExecuteCommand();
// Wait for user
Console.ReadKey();
}
}
/// <summary>
/// The 'Command' abstract class
/// </summary>
public abstract class Command
{
protected Receiver receiver;
// Constructor
public Command(Receiver receiver)
{
this.receiver = receiver;
}
public abstract void Execute();
}
/// <summary>
/// The 'ConcreteCommand' class
/// </summary>
public class ConcreteCommand : Command
{
// Constructor
public ConcreteCommand(Receiver receiver) :
base(receiver)
{
}
public override void Execute()
{
receiver.Action();
}
}
/// <summary>
/// The 'Receiver' class
/// </summary>
public class Receiver
{
public void Action()
{
Console.WriteLine("Called Receiver.Action()");
}
}
/// <summary>
/// The 'Invoker' class
/// </summary>
public class Invoker
{
Command command;
public void SetCommand(Command command)
{
this.command = command;
}
public void ExecuteCommand()
{
command.Execute();
}
}
}
This real-world code demonstrates the Command pattern used in a simple calculator with unlimited number of undo's and redo's. Note that in C# the word 'operator' is a keyword. Prefixing it with '@' allows using it as an identifier.
using System;
using System.Collections.Generic;
namespace Command.RealWorld
{
/// <summary>
/// Command Design Pattern
/// </summary>
public class Program
{
public static void Main(string[] args)
{
// Create user and let her compute
User user = new User();
// User presses calculator buttons
user.Compute('+', 100);
user.Compute('-', 50);
user.Compute('*', 10);
user.Compute('/', 2);
// Undo 4 commands
user.Undo(4);
// Redo 3 commands
user.Redo(3);
// Wait for user
Console.ReadKey();
}
}
/// <summary>
/// The 'Command' abstract class
/// </summary>
public abstract class Command
{
public abstract void Execute();
public abstract void UnExecute();
}
/// <summary>
/// The 'ConcreteCommand' class
/// </summary>
public class CalculatorCommand : Command
{
char @operator;
int operand;
Calculator calculator;
// Constructor
public CalculatorCommand(Calculator calculator,
char @operator, int operand)
{
this.calculator = calculator;
this.@operator = @operator;
this.operand = operand;
}
// Gets operator
public char Operator
{
set { @operator = value; }
}
// Get operand
public int Operand
{
set { operand = value; }
}
// Execute new command
public override void Execute()
{
calculator.Operation(@operator, operand);
}
// Unexecute last command
public override void UnExecute()
{
calculator.Operation(Undo(@operator), operand);
}
// Returns opposite operator for given operator
private char Undo(char @operator)
{
switch (@operator)
{
case '+': return '-';
case '-': return '+';
case '*': return '/';
case '/': return '*';
default:
throw new
ArgumentException("@operator");
}
}
}
/// <summary>
/// The 'Receiver' class
/// </summary>
public class Calculator
{
int curr = 0;
public void Operation(char @operator, int operand)
{
switch (@operator)
{
case '+': curr += operand; break;
case '-': curr -= operand; break;
case '*': curr *= operand; break;
case '/': curr /= operand; break;
}
Console.WriteLine(
"Current value = {0,3} (following {1} {2})",
curr, @operator, operand);
}
}
/// <summary>
/// The 'Invoker' class
/// </summary>
public class User
{
// Initializers
Calculator calculator = new Calculator();
List<Command> commands = new List<Command>();
int current = 0;
public void Redo(int levels)
{
Console.WriteLine("\n---- Redo {0} levels ", levels);
// Perform redo operations
for (int i = 0; i < levels; i++)
{
if (current < commands.Count - 1)
{
Command command = commands[current++];
command.Execute();
}
}
}
public void Undo(int levels)
{
Console.WriteLine("\n---- Undo {0} levels ", levels);
// Perform undo operations
for (int i = 0; i < levels; i++)
{
if (current > 0)
{
Command command = commands[--current] as Command;
command.UnExecute();
}
}
}
public void Compute(char @operator, int operand)
{
// Create command operation and execute it
Command command = new CalculatorCommand(calculator, @operator, operand);
command.Execute();
// Add command to undo list
commands.Add(command);
current++;
}
}
}
The .NET optimized code demonstrates the same code as above but uses
more modern C# and .NET features.
Here is an elegant C# Command solution.
namespace Command.NetOptimized;
using static System.Console;
using System;
using System.Collections.Generic;
/// <summary>
/// Command Design Pattern
/// </summary>
public class Program
{
public static void Main()
{
// Create user and let her compute
var user = new User();
// Issue several compute commands
user.Compute('+', 100);
user.Compute('-', 50);
user.Compute('*', 10);
user.Compute('/', 2);
// Undo 4 commands
user.Undo(4);
// Redo 3 commands
user.Redo(3);
// Wait for user
ReadKey();
}
}
/// <summary>
/// The 'Command' interface
/// </summary>
public interface ICommand
{
void Execute();
void UnExecute();
}
/// <summary>
/// The 'ConcreteCommand' class
/// </summary>
public class CalculatorCommand( Calculator calculator,
char @operator,
int operand) : ICommand
{
// Sets operator
public char Operator { set => @operator = value; }
// Sets operand
public int Operand { set => operand = value; }
// Execute command
public void Execute()
{
calculator.Operation(@operator, operand);
}
// Unexecute command
public void UnExecute()
{
calculator.Operation(Undo(@operator), operand);
}
// Return opposite operator for given operator
private static char Undo(char @operator)
{
return @operator switch
{
'+' => '-',
'-' => '+',
'*' => '/',
'/' => '*',
_ => throw new ArgumentException("@operator"),
};
}
}
/// <summary>
/// The 'Receiver' class
/// </summary>
public class Calculator
{
private int current = 0;
// Perform operation for given operator and operand
public void Operation(char @operator, int operand)
{
switch (@operator)
{
case '+': current += operand; break;
case '-': current -= operand; break;
case '*': current *= operand; break;
case '/': current /= operand; break;
}
WriteLine(
"Current value = {0,3} (following {1} {2})",
current, @operator, operand);
}
}
/// <summary>
/// The 'Invoker' class
/// </summary>
public class User
{
private readonly Calculator calculator = new();
private readonly List<ICommand> commands = [];
private int current = 0;
// Redo original commands
public void Redo(int levels)
{
WriteLine($"\n---- Redo {levels} levels ");
// Perform redo operations
for (int i = 0; i < levels; i++)
{
if (current < commands.Count - 1)
{
commands[current++].Execute();
}
}
}
// Undo prior commands
public void Undo(int levels)
{
WriteLine($"\n---- Undo {levels} levels ");
// Perform undo operations
for (int i = 0; i < levels; i++)
{
if (current > 0)
{
commands[--current].UnExecute();
}
}
}
// Compute new value given operator and operand
public void Compute(char @operator, int operand)
{
// Create command operation and execute it
var command = new CalculatorCommand(calculator, @operator, operand);
command.Execute();
// Add command to undo list
commands.Add(command);
current++;
}
}