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.
-
-
-
- public class Account {
- public string CustomerName {
- get;
- set;
- }
- public double AccountBalance {
- get;
- set;
- }
- public Account(string customerName, double accountBalance) {
- CustomerName = customerName;
- AccountBalance = accountBalance;
- }
- }
-
-
-
-
- public interface ITransaction {
- void ExecuteCommand();
- bool IsCommandCompleted {
- get;
- set;
- }
- }
-
-
-
- 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;
- }
- }
-
-
-
- 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;
- }
- }
- }
-
-
-
- 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) {
-
- TransactionManager manager = new CommandPattern.TransactionManager();
- Account accountAshish = new CommandPattern.Account("Ashish", 0);
- ITransaction depositTransaction = new Deposit(accountAshish, 100);
- manager.AddTransaction(depositTransaction);
- manager.ProcessPendingTransactions();
-
- ITransaction withdrawTransaction = new Withdraw(accountAshish, 200);
- manager.AddTransaction(withdrawTransaction);
- manager.ProcessPendingTransactions();
- var pendingTransaction = manager.HasInCompleteTransactions;
- Console.WriteLine(pendingTransaction);
- Console.ReadKey();
-
-
- ITransaction anotherDepositTransaction = new Deposit(accountAshish, 200);
- manager.AddTransaction(anotherDepositTransaction);
- manager.ProcessPendingTransactions();
- Console.WriteLine(manager.HasInCompleteTransactions);
- Console.ReadKey();
- if (manager.HasInCompleteTransactions) {
-
- ReattemptPendingTransactions(manager);
- }
- Console.WriteLine(manager.HasInCompleteTransactions);
- Console.ReadKey();
-
- 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();
- }
- }
- }
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
-
-
-
- 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();
- }
- }
- }
-
-
-
- public class Account {
- public string CustomerName {
- get;
- set;
- }
- public double AccountBalance {
- get;
- set;
- }
- public Account(string customerName, double accountBalance) {
- CustomerName = customerName;
- AccountBalance = accountBalance;
- }
- }
-
-
-
-
- public interface ITransaction {
- int Id {
- get;
- set;
- }
- void ExecuteCommand();
- bool IsCommandCompleted {
- get;
- set;
- }
- CommandState Status {
- get;
- set;
- }
- void Undo();
- }
-
-
-
- public enum CommandState {
- UnProcessed,
- ExecutionFailed,
- ExecutionSuccessed,
- UndoDone,
- UndoFailed
- }
-
-
-
- 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;
- }
- }
- }
-
-
-
- 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;
- }
- }
-
-
-
- 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.