Command Design Pattern is a type of Behavioral Design Pattern.
Behavioral Design Pattern
It's about object communication, their responsibilities, and how they communicate to each other.
There might be a situation where we want to encapsulate the required information in an Object to perform some task and the task can be performed many times or whenever it's required. The command design pattern is the solution. It also gives you an easy way to implement Undo() that can just undo multiple commands.
- Implementation - Typically, Implementation of Command Pattern is divided into 4 parts.
- Command - That executes an action.
- Receiver - Objects that receive the action from the command.
Invoker: Invoke the Commands to execute their actions. The Invoker may be a queue that holds commands for future execution, or holds such commands which can be used by different applications. The machine can be used to execute commands multiple times or can be used to undo the command.
Client
Client is the main program that asks for a command to be executed.
Consider the case of a banking application which is capable of making transactions i.e. Transfer, Deposit, Withdraw etc.
Let's identify each part of the command design pattern we discussed above.
Account of a customer ? Think, what it should be ?
Command ? Read command's definition again.......It says that it executes an action, but what action Account will it execute? Actions such as increment in account balance or decrements in account balance can be executed on Account by the Commands Deposit/Withdraw . So, if it receives actions, it means account is a Receiver.
So, Receiver is Account, and Command Deposit will add money from Account Balance and Withdraw command will subtract money from Account Balance.
/// <summary>
/// Reciever of Command
/// </summary>
public class Account {
public string CustomerName {
get;
set;
}
public double AccountBalance {
get;
set;
}
public Account(string customerName, double accountBalance) {
CustomerName = customerName;
AccountBalance = accountBalance;
}
}
/// <summary>
/// Defines the action of Command those can be executed - will be called by Invoker
/// IsCommandCompleted signals the command is completed and can be removed from Invoker
/// </summary>
public interface ITransaction {
void ExecuteCommand();
bool IsCommandCompleted {
get;
set;
}
}
/// <summary>
/// Deposit Command
/// </summary>
public class Deposit: ITransaction {
private readonly Account _account;
private readonly double _amount;
public bool IsCommandCompleted {
get;
set;
}
public Deposit(Account account, double amount) {
_account = account;
_amount = amount;
IsCommandCompleted = false;
}
public void ExecuteCommand() {
_account.AccountBalance += _amount;
IsCommandCompleted = true;
}
}
/// <summary>
/// Withdraw Command
/// </summary>
public class Withdraw: ITransaction {
private readonly Account _account;
private readonly double _amount;
public bool IsCommandCompleted {
get;
set;
}
public Withdraw(Account account, double amount) {
_account = account;
_amount = amount;
IsCommandCompleted = false;
}
public void ExecuteCommand() {
if (_account.AccountBalance >= _amount) {
_account.AccountBalance -= _amount;
IsCommandCompleted = true;
}
}
}
/// <summary>
/// Transfer Command
/// </summary>
public class Transfer: ITransaction {
private readonly Account _fromAccount;
private readonly Account _toAccount;
private readonly double _amount;
public bool IsCommandCompleted {
get;
set;
}
public Transfer(Account fromAccount, Account toAccount, double amount) {
_fromAccount = fromAccount;
_toAccount = toAccount;
IsCommandCompleted = false;
}
public void ExecuteCommand() {
_fromAccount.AccountBalance -= _amount;
_toAccount.AccountBalance += _amount;
IsCommandCompleted = true;
}
Lets write Invoker,
public class TransactionManager {
private readonly IList < ITransaction > _transactions = new List < ITransaction > ();
public bool HasInCompleteTransactions {
get {
return _transactions.Any(x => !x.IsCommandCompleted);
}
}
public IList < ITransaction > GetPendingTransactions() {
return _transactions ? .Where(x => !x.IsCommandCompleted) ? .ToList();
}
public void AddTransaction(ITransaction transaction) {
_transactions.Add(transaction);
}
public void ProcessPendingTransactions() {
foreach(var transaction in _transactions.Where(x => !x.IsCommandCompleted)) {
transaction.ExecuteCommand();
}
}
}
}
The Client is responsible to create commands and pass them to the Invoker. The Commands will be held in the _transactions list, until the Client calls ProcessInCompleteTransactions. Then, the Invoker will try to execute each incomplete Command.
Invoker should not be aware of anything about what the Command can do, or what inputs it needs. All it needs to know is that the Command should be executed.
We will here simulate the client using our Console application to demonstrate.
class Program {
static void Main(string[] args) {
//Add 100 to the account - there should not be any pending job
TransactionManager manager = new CommandPattern.TransactionManager();
Account accountAshish = new CommandPattern.Account("Ashish", 0);
ITransaction depositTransaction = new Deposit(accountAshish, 100);
manager.AddTransaction(depositTransaction);
manager.ProcessPendingTransactions();
//try to withdraw 200 - transction will be pending since the balance is account is low
ITransaction withdrawTransaction = new Withdraw(accountAshish, 200);
manager.AddTransaction(withdrawTransaction);
manager.ProcessPendingTransactions();
var pendingTransaction = manager.HasInCompleteTransactions;
Console.WriteLine(pendingTransaction);
Console.ReadKey();
//add 200- still withdraw trasaction would be pending since we are adding money after withdraw failed attempt,
//we would need to execute failed transacction again
ITransaction anotherDepositTransaction = new Deposit(accountAshish, 200);
manager.AddTransaction(anotherDepositTransaction);
manager.ProcessPendingTransactions();
Console.WriteLine(manager.HasInCompleteTransactions);
Console.ReadKey();
if (manager.HasInCompleteTransactions) {
//reattempt failed transactions
ReattemptPendingTransactions(manager);
}
Console.WriteLine(manager.HasInCompleteTransactions);
Console.ReadKey();
//Try Transfer
Account accountAvinash = new Account("Avinash", 10);
ITransaction transferTransaction = new Transfer(accountAshish, accountAvinash, 10);
manager.AddTransaction(transferTransaction);
manager.ProcessPendingTransactions();
Console.WriteLine("Ashish account balance:" + accountAshish.AccountBalance);
Console.WriteLine("Anjali account balance:" + accountAvinash.AccountBalance);
Console.ReadKey();
}
private static void ReattemptPendingTransactions(TransactionManager manager) {
var pendingTransactions = manager.GetPendingTransactions();
foreach(var item in pendingTransactions) {
item.ExecuteCommand();
}
}
}
Enhancement Undo
Suppose you want to undo the command. Modify your code, add Undo in your command.
You should facilitate your program with the undo all command and undo a particular command. In case of a particular command undo, you would need some kind of identifier which can uniquely identify the command (i.e. Id) and perform undo on it. We should be able to Undo successful commands also, we can have some status of command which tells us if command is executed successfully or not, unprocessed, Undo Successful, Undo Failed etc.
Transaction would look like,
public interface ITransaction {
int Id {
get;
set;
}
void ExecuteCommand();
bool IsCommandCompleted {
get;
set;
}
void Undo();
}
Implement modified interface in all the Commands Deposit, Withdraw and Transfer
Create an enum to set Command state
/// <summary>
/// Command sate enum
/// </summary>
public enum CommandState {
UnProcessed,
ExecutionFailed,
ExecutionSuccessed,
UndoDone,
UndoFailed
}
public interface ITransaction {
int Id {
get;
set;
}
void ExecuteCommand();
bool IsCommandCompleted {
get;
set;
}
CommandState Status {
get;
set;
}
void Undo();
} === === === === === == Other Updated Classes === === === === === === === === === === === == private static void ReattemptPendingTransactions(TransactionManager manager) {
var pendingTransactions = manager.GetPendingTransactions();
foreach(var item in pendingTransactions) {
item.ExecuteCommand();
}
}
}
/// <summary>
/// Reciever of Command
/// </summary>
public class Account {
public string CustomerName {
get;
set;
}
public double AccountBalance {
get;
set;
}
public Account(string customerName, double accountBalance) {
CustomerName = customerName;
AccountBalance = accountBalance;
}
}
/// <summary>
/// Defines the action of Command those can be executed - will be called by Invoker
/// IsCommandCompleted signals the command is completed and can be removed from Invoker
/// </summary>
public interface ITransaction {
int Id {
get;
set;
}
void ExecuteCommand();
bool IsCommandCompleted {
get;
set;
}
CommandState Status {
get;
set;
}
void Undo();
}
/// <summary>
/// Command sate enum
/// </summary>
public enum CommandState {
UnProcessed,
ExecutionFailed,
ExecutionSuccessed,
UndoDone,
UndoFailed
}
/// <summary>
/// Deposit Command
/// </summary>
public class Deposit: ITransaction {
private readonly Account _account;
private readonly double _amount;
public bool IsCommandCompleted {
get;
set;
}
public int Id {
get;
set;
}
public CommandState Status {
get {
throw new NotImplementedException();
}
set {
throw new NotImplementedException();
}
}
public Deposit(int Id, Account account, double amount) {
this.Id = Id;
_account = account;
_amount = amount;
IsCommandCompleted = false;
Status = CommandState.UnProcessed;
}
public void ExecuteCommand() {
_account.AccountBalance += _amount;
IsCommandCompleted = true;
Status = CommandState.ExecutionSuccessed;
}
public void Undo() {
if (_account.AccountBalance >= _amount) {
_account.AccountBalance -= _amount;
Status = CommandState.UndoDone;
} else {
Status = CommandState.UndoFailed;
}
}
}
/// <summary>
/// Withdraw Command
/// </summary>
public class Withdraw: ITransaction {
private readonly Account _account;
private readonly double _amount;
public bool IsCommandCompleted {
get;
set;
}
public int Id {
get;
set;
}
public CommandState Status {
get;
set;
}
public Withdraw(int Id, Account account, double amount) {
_account = account;
_amount = amount;
IsCommandCompleted = false;
this.Id = Id;
Status = CommandState.UnProcessed;
}
public void ExecuteCommand() {
if (_account.AccountBalance >= _amount) {
_account.AccountBalance -= _amount;
IsCommandCompleted = true;
Status = CommandState.ExecutionSuccessed;
} else {
Status = CommandState.ExecutionFailed;
}
}
public void Undo() {
_account.AccountBalance += _amount;
Status = CommandState.UndoDone;
}
}
/// <summary>
/// Transfer Command
/// </summary>
public class Transfer: ITransaction {
private readonly Account _fromAccount;
private readonly Account _toAccount;
private readonly double _amount;
public bool IsCommandCompleted {
get;
set;
}
public int Id {
get;
set;
}
public CommandState Status {
get;
set;
}
public Transfer(int Id, Account fromAccount, Account toAccount, double amount) {
_fromAccount = fromAccount;
_toAccount = toAccount;
IsCommandCompleted = false;
_amount = amount;
this.Id = Id;
Status = CommandState.UnProcessed;
}
public void ExecuteCommand() {
if (_fromAccount.AccountBalance >= +_amount) {
_fromAccount.AccountBalance -= _amount;
_toAccount.AccountBalance += _amount;
IsCommandCompleted = true;
Status = CommandState.ExecutionSuccessed;
} else {
Status = CommandState.ExecutionFailed;
}
}
public void Undo() {
if (_toAccount.AccountBalance >= _amount) {
_toAccount.AccountBalance -= _amount;
_fromAccount.AccountBalance += _amount;
Status = CommandState.UndoDone;
} else {
Status = CommandState.UndoFailed;
}
}
}
public class TransactionManager {
private readonly IList < ITransaction > _transactions = new List < ITransaction > ();
public bool HasInCompleteTransactions {
get {
return _transactions.Any(x => !x.IsCommandCompleted);
}
}
public IList < ITransaction > GetPendingTransactions() {
return _transactions ? .Where(x => !x.IsCommandCompleted) ? .ToList();
}
public void AddTransaction(ITransaction transaction) {
_transactions.Add(transaction);
}
public void ProcessPendingTransactions() {
foreach(var transaction in _transactions.Where(x => !x.IsCommandCompleted)) {
transaction.ExecuteCommand();
}
}
Note
I won't create a client for these extended functionalities, I want you to try this. If you find any difficulty, please reach out to me through the Contact Us Page.
Command Design Pattern is often used with message queue applications such as, logging. In case of sudden system shut down/ crash, our system would be able read the incomplete commands from the queue, and resume without any data loss. Another scenario is if you want to interact with some service and that service is not available, this pattern will help you in reattempting the operation once service is up again.
Since it adds the complexity to the system, it is recommended to use this pattern in the big system where reliability is important.
HostForLIFE.eu ASP.NET Core 2.2.1 Hosting
European best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.