Skip to main content

Command Design Pattern in C#

Command Design Pattern in C#

Command Design Pattern is a type of Behavioral Design Pattern.

Behavioral Design Pattern : It's about object communication, their responsibilities and how they communicate each other.

There might be a situation where we want to encapsulate required information in a Object to perform some task and the task can be perform many times or whenever its required. The command design pattern is the solution. It also gives you easy way to implement Undo() that can just undo multiple command.

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 hold such commands which can be used by different application/machine can be used to execute commands multiple times or can be used to undo the command.
Client: 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 command design pattern we discussed above.
Account of a customer ? Think, what it should be ?
Command ? Read command's definition again.......It says that executes action, What action Account will execute ? Actions such increment in account balance or decrements in account balance can be executed on Account by the Commands Deposit/Withdraw . So, 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 Command 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 needed. 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 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.

ITransaction 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 and enum too 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 client for these extended functionality, I want you to try this. If you find any difficulty, please reach out to me through 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.



Comments

Popular posts from this blog

Abstract Factory Design Pattern

Abstract Factory : return families of related or dependent objects without specifying their concrete classes. AbstractFactory- IVehicle ConcreteFactory - Maruti, Honda AbstractProduct- IDiesel, IPetrol Product- DezirePetrol, ZenDiesel, AmazeDiesel, CityPetrol Client- This is a class which uses AbstractFactory and AbstractProduct interfaces to create a family of related objects Implementation is pretty much straight forward: Since now you have good understanding of Factory, there is nothing much to explain. using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AbstractFactory {     class Program     {         static void Main(string[] args)         {             IVehicle marutiVehicle = new Maruti();             CarClient marutiClient = new CarClient(marutiVehicle);             Console.WriteLine("********* MARUTI **********");             Console.WriteLine(marutiClient.Get

Database Factory Design Pattern C#

Many developers has confusion on how to write database code in application. I will discuss several approaches here and try to provide explanation on each approach. Approach 1 : Easiest approach which is very straight forward which will work absolutely fine. The issues we will get to manage the project in terms of software principles. Like if you going to change Database provider, you would need to change using statements and other issue like mixing Database code with business logic etc. I will not go in detail here, I am assuming you already have an understanding of design principles. I am just exploring different approaches to achieve the database connectivity. using System.Collections.Generic; using System.Data.SqlClient; namespace DatabaseFactory {     class Program     {         static void Main(string[] args)         {             var employees = SomeDabOperation();         }         private static List<Employee> SomeDabOperation()         {             u