What is the Command Design Pattern?
The Command Design Pattern is a behavioral pattern that puts the logic to execute a function into a separate class/object, so it can be executed independently of the model class/object.
It’s especially useful for build enterprise-level programs, as it can improve reliability and scalability.
Parts of the Command Design Pattern
There are four parts to the Command pattern.
Receiver: Business object that “receives” the action from the Command.
Command: Classes that execute an action on a receiver object.
Invoker: Class that tells the Command objects to execute their actions.
Client: The main program that uses the other parts.
For this example, we’ll write a program for a bank.
We want to deposit money into an account, withdraw money from an account, and transfer money between accounts.
Non-Pattern version of code
The non-pattern version of the class has Deposit and Withdraw functions, which change the balance as soon as they’re called.
namespace DesignPatternsCSharpNet6.Command.NonPatternVersion; // This is a sample of a non-command version, // where functions are executed on an object immediately. public class Account { public string OwnerName { get; set; } public decimal Balance { get; set; } public Account(string ownerName, decimal balance) { OwnerName = ownerName; Balance = balance; } public void Deposit(decimal amount) { Balance += amount; } public void Withdraw(decimal amount) { if(amount > Balance) { throw new ArgumentOutOfRangeException("Overdraft error"); } Balance -= amount; } }
Sometimes, you may not want to execute functions immediately.
You can use the Command design pattern to add work to a queue, to be done separately.
Command objects can be added to a queue and possibly be handled by multiple Invoker objects running on different computers. This is one way to make a program more scalable.
It also gives you the potential to retry, in case the program crashed, or there was a problem executing the Command object’s code.
Source code that implements the Command Design Pattern
Receiver class
The Receiver class “receives” the actions of the Command objects – It has its properties modified when Command objects are run.
In this example, the Receiver class is a bank account. The Command objects will modify the balance of the account objects.
namespace DesignPatternsCSharpNet6.Command.PatternVersion; public class Account { public string OwnerName { get; set; } public decimal Balance { get; set; } public Account(string ownerName, decimal balance) { OwnerName = ownerName; Balance = balance; } }
Command class interface
Our Command objects execute a function on the Account objects.
We have a Deposit class to add money, a Withdraw class to remove money, and a Transfer class to remove money from one account and add it to another.
All the Command classes will implement the ITransaction interface.
If you aren’t familiar with an interface, it’s just a way to say, “Every class that implements this interface, must have these properties/methods/etc.” Then, you can create functions that accept parameters using the interface as a data type, instead of a concrete class.
The Execute function and the Status properties are the things defined in this interface, because those are what the Invoker object need to use from the Command objects.
I’m using an interface here; however, you could do the same thing with a base class – with the Invoker expecting Command objects that inherit from the base class.
namespace DesignPatternsCSharpNet6.Command.PatternVersion; public interface ITransaction { ExecutionStatus Status { get; set; } void Execute(); }
namespace DesignPatternsCSharpNet6.Command.PatternVersion; public enum ExecutionStatus { Unprocessed, InsufficientFunds, ExecuteFailed, ExecuteSucceeded }
Command classes
Notice that the constructors are different – the Transfer class constructor takes two accounts.
That’s OK. The Invoker only cares that every Command object has an Execute function and a Status property. The command object can have additional properties and functions, if we need them for some other purpose, but the Invoker doesn’t care.
namespace DesignPatternsCSharpNet6.Command.PatternVersion; public class Deposit : ITransaction { private readonly Account _account; private readonly decimal _amount; public ExecutionStatus Status { get; set; } public Deposit(Account account, decimal amount) { _account = account; _amount = amount; Status = ExecutionStatus.Unprocessed; } public void Execute() { _account.Balance += _amount; Status = ExecutionStatus.ExecuteSucceeded; } }
namespace DesignPatternsCSharpNet6.Command.PatternVersion; public class Withdraw : ITransaction { private readonly Account _account; private readonly decimal _amount; public ExecutionStatus Status { get; set; } public Withdraw(Account account, decimal amount) { _account = account; _amount = amount; Status = ExecutionStatus.Unprocessed; } public void Execute() { if(_account.Balance >= _amount) { _account.Balance -= _amount; Status = ExecutionStatus.ExecuteSucceeded; } else { Status = ExecutionStatus.InsufficientFunds; } } }
namespace DesignPatternsCSharpNet6.Command.PatternVersion; public class Transfer : ITransaction { private readonly decimal _amount; private readonly Account _fromAccount; private readonly Account _toAccount; public ExecutionStatus Status { get; set; } public Transfer(Account fromAccount, Account toAccount, decimal amount) { _fromAccount = fromAccount; _toAccount = toAccount; _amount = amount; Status = ExecutionStatus.Unprocessed; } public void Execute() { if(_fromAccount.Balance >= _amount) { _fromAccount.Balance -= _amount; _toAccount.Balance += _amount; Status = ExecutionStatus.ExecuteSucceeded; } else { Status = ExecutionStatus.InsufficientFunds; } } }
NOTE: The Execute methods in Withdraw and Transfer will not run if there is not enough money in the account. So, Status will be InsufficientFunds, and the Command object will still be a pending transaction in the TransactionManager.
Invoker class
TransactionManager is the Invoker. It holds the Command objects and tries to perform the Execute on each one when the Client asks it to process all the Command objects.
namespace DesignPatternsCSharpNet6.Command.PatternVersion; public class TransactionManager { private readonly List<ITransaction> _transactions = new List<ITransaction>(); public bool HasPendingTransactions => _transactions.Any(x => x.Status == ExecutionStatus.Unprocessed || x.Status == ExecutionStatus.InsufficientFunds || x.Status == ExecutionStatus.ExecuteFailed); public void AddTransaction(ITransaction transaction) { _transactions.Add(transaction); } public void ProcessPendingTransactions() { // Execute transactions that are unprocessed, // or couldn't be precessed successfully before. foreach(ITransaction transaction in _transactions.Where(x => x.Status == ExecutionStatus.Unprocessed || x.Status == ExecutionStatus.InsufficientFunds || x.Status == ExecutionStatus.ExecuteFailed)) { try { transaction.Execute(); } catch (Exception e) { transaction.Status = ExecutionStatus.ExecuteFailed; } } } }
Client class
The Client creates Command objects and sends them to the Invoker – in this sample, through the AddTransaction function.
For this example, TestTransactionManager (a unit test class) is the simulated Client.
The Commands are held in the _transactions list until the Client calls ProcessPendingTransacations. Then, the Invoker will try to Execute each Command that has not already been successfully completed.
The Invoker doesn’t know anything about what the Command objects do, or what parameters they need. All it knows is that the objects have an Execute() function that can be called, and a Status property that can be set.
We could create additional Command classes: OpenAccount, CloseAccount, PayInterest, etc. If they implement the ITransaction interface, the Invoker could process them.
This makes it easier to add more capabilities to the program. Just create a new Command object for the new business function, have it implement the ITransaction interface, and pass it into the Invoker with the rest of the other Command objects.
using DesignPatternsCSharpNet6.Command.PatternVersion; namespace Test.DesignPatternsCSharpNet6.CommandPattern.PatternVersion; public class TestCommandPattern { [Fact] public void Test_AllTransactionsSuccessful() { TransactionManager transactionManager = new TransactionManager(); Account suesAccount = new Account("Sue Smith", 0); Deposit deposit = new Deposit(suesAccount, 100); transactionManager.AddTransaction(deposit); // Command has been added to the queue, but not executed. Assert.True(transactionManager.HasPendingTransactions); Assert.Equal(0, suesAccount.Balance); // This executes the code in the command objects. transactionManager.ProcessPendingTransactions(); Assert.False(transactionManager.HasPendingTransactions); Assert.Equal(100, suesAccount.Balance); // Add a withdrawal, apply it, and verify the balance changed. Withdraw withdrawal = new Withdraw(suesAccount, 50); transactionManager.AddTransaction(withdrawal); transactionManager.ProcessPendingTransactions(); Assert.False(transactionManager.HasPendingTransactions); Assert.Equal(50, suesAccount.Balance); } [Fact] public void Test_OverdraftRemainsInPendingTransactions() { TransactionManager transactionManager = new TransactionManager(); // Create an account with a balance of 75 Account bobsAccount = new Account("Bob Jones", 75); // The first command is a withdrawal that is larger than the account's balance. // It will not be executed, because of the check in Withdraw.Execute. // The deposit will be successful. transactionManager.AddTransaction(new Withdraw(bobsAccount, 100)); transactionManager.AddTransaction(new Deposit(bobsAccount, 75)); transactionManager.ProcessPendingTransactions(); // The withdrawal of 100 was not completed, // because there was not enough money in the account. // So, it is still pending. Assert.True(transactionManager.HasPendingTransactions); Assert.Equal(150, bobsAccount.Balance); // The pending transactions (the withdrawal of 100), should execute now. transactionManager.ProcessPendingTransactions(); Assert.False(transactionManager.HasPendingTransactions); Assert.Equal(50, bobsAccount.Balance); } [Fact] public void Test_Transfer() { TransactionManager transactionManager = new TransactionManager(); Account checking = new Account("Mike Brown", 1000); Account savings = new Account("Mike Brown", 100); transactionManager.AddTransaction(new Transfer(checking, savings, 750)); transactionManager.ProcessPendingTransactions(); Assert.Equal(250, checking.Balance); Assert.Equal(850, savings.Balance); } }
The three test methods simulate what our client program might do and verify that the Invoker executes the Commands and properly set the Status property.
Where I’ve found the Command Design Pattern useful
I often use the Command Design Pattern with a message queue application.
You can store Command objects into the queue. If the server has a problem, like the program crashing, you can restart the queue and have the program continue where it left off. If the program was doing all its processing in-memory, and the program crashed, it would lose whatever incomplete work it had in memory.
I’ve used this with programs that submit data to web services. If the web service isn’t working, we still have the command object in the queue. When the web service is working again, the queue will try to Execute all the unsent Command objects.
This pattern is also useful for websites or web services that may get a lot of requests. You can start another server, run another instance of the program with the Invoker class, and it can start processing a portion of those requests.
You normally don’t need to use the Command design pattern for personal projects. It adds complexity that usually isn’t needed for small, personal programs. However, it can be extremely useful for large business systems that need to be reliable and scalable.