Press "Enter" to skip to content

Command Design Pattern in C#

Last updated on September 15, 2023

This article will demonstrate the “Command” design pattern.

This design pattern is a little complex. It uses several different parts. However, it’s useful to know when you build enterprise-level programs, which need to be reliable and scalable.

 

View the video version here:

 

What it is

Normally, when you want to do something to an object, you call a function on that object. The function starts to execute as soon as you call it.

Here’s a typical, bank account class, where the Deposit and Withdraw functions are immediately executed when they’re called.

public class Account_NonCommand
{
    public string OwnerName { get; set; }
    public decimal Balance { get; set; }
    public Account_NonCommand(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 don’t want to do execute your functions immediately.

You can use the Command pattern to add work to a queue, to be done later. You can use it to retry, if a command cannot execute properly. You might be able to use this to add “undo” capabilities to a program.

 

Parts of the Command Design Pattern

There are four parts to the Command pattern.

Command: Classes that execute an action (perform a function).

Receiver: Business objects that “receive” the action from the Command.

Invoker: This class tells the Commands to execute their actions. The Invoker can sometimes be a queue (when it holds commands to be executed later), a pool (when it holds commands that can be executed by different programs/computers), or let you do more things with your commands (retry failed commands, undo commands that were executed, etc.)

Client: The main program that uses the other parts.

 

Examples

For this example, we’ll write a program for a bank.

We want to deposit money, withdraw money, and do transfers between accounts.

 

Account.cs

This is the Receiver. Our commands will execute something on the Account objects. For example, a Deposit command will increase the Account object’s Balance.

public class Account
{
    public string OwnerName { get; set; }
    public decimal Balance { get; set; }
    public Account(string ownerName, decimal balance)
    {
        OwnerName = ownerName;
        Balance = balance;
    }
}

 

 

ITransaction.cs

This interface defines what must exist in our Command objects.

The Execute method is what will be called by the Invoker. IsCompleted will be used to let us know is the Command executed successfully – so we can remove the completed Commands from the Invoker’s queue.

I’m using an interface here; however, you could do the same thing with an abstract base class.

public interface ITransaction
{
    bool IsCompleted { get; set; }
    void Execute();
}

 

These are the Command objects, which 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.”

Notice that the constructors are different – the Transfer Command takes two accounts. That’s OK. The thing we care about is that every Command has an Execute function, and it has everything it needs to perform the business function.

 

Deposit.cs

public class Deposit : ITransaction
{
    private readonly Account _account;
    private readonly decimal _amount;
    public bool IsCompleted { get; set; }
    public Deposit(Account account, decimal amount)
    {
        _account = account;
        _amount = amount;
        IsCompleted = false;
    }
    public void Execute()
    {
        _account.Balance += _amount;
        IsCompleted = true;
    }
}

 

Withdraw.cs

public class Withdraw : ITransaction
{
    private readonly Account _account;
    private readonly decimal _amount;
    public bool IsCompleted { get; set; }
    public Withdraw(Account account, decimal amount)
    {
        _account = account;
        _amount = amount;
        IsCompleted = false;
    }
    public void Execute()
    {
        if(_account.Balance >= _amount)
        {
            _account.Balance -= _amount;
            IsCompleted = true;
        }
    }
}

Transfer.cs

public class Transfer : ITransaction
{
    private readonly decimal _amount;
    private readonly Account _fromAccount;
    private readonly Account _toAccount;
    public bool IsCompleted { get; set; }
    public Transfer(Account fromAccount, Account toAccount, decimal amount)
    {
        _fromAccount = fromAccount;
        _toAccount = toAccount;
        _amount = amount;
        IsCompleted = false;
    }
    public void Execute()
    {
        if(_fromAccount.Balance >= _amount)
        {
            _fromAccount.Balance -= _amount;
            _toAccount.Balance += _amount;
            IsCompleted = true;
        }
    }
}

 

NOTE: The Execute methods in Withdraw and Transfer will not run if there is not enough money in the account. So, IsCompleted will still be False, and the Command object will still be a pending transaction in the TransactionManager.

 

TransactionManager.cs

TransactionManager is the Invoker. It holds the Command objects, and tries to perform the Execute on each one (when the Client asks to process all the Command objects).

using System.Collections.Generic;
using System.Linq;
public class TransactionManager
{
    private readonly List<ITransaction> _transactions = new List<ITransaction>();
    public bool HasPendingTransactions
    {
        get { return _transactions.Any(x => !x.IsCompleted); }
    }
    public void AddTransaction(ITransaction transaction)
    {
        _transactions.Add(transaction);
    }
    public void ProcessPendingTransactions()
    {
        // Apply transactions in the order they were added.
        foreach(ITransaction transaction in _transactions.Where(x => !x.IsCompleted))
        {
            transaction.Execute();
        }
    }
}

 

The Client creates Command objects and sends them to the Invoker – in this sample, through the AddTransaction function. The Commands will be held in the _transactions list, until the Client calls ProcessPendingTransacations. Then, the Invoker will try to Execute each Command that has not already been completed.

Notice that the Invoker doesn’t know anything about what the Command objects do, or what parameters they need. All it needs to know is that the Command can be executed.

We could create additional Command classes: OpenAccount, CloseAccount, PayInterest, etc. And, if they implement the ITransaction interface, the Invoker can process them. This makes it easier to add more capabilities to the program – and they would automatically be able to use the apply/retry/undo/etc. features that are in the Invoker.

 

TestTransactionManager.cs

For this example, TestTransactionManager (a unit test class) is the simulated Client.

 

using Engine.CommandPattern;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestEngine.CommandPattern
{
    [TestClass]
    public class TestTransactionManager
    {
        [TestMethod]
        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.IsTrue(transactionManager.HasPendingTransactions);
            Assert.AreEqual(0, suesAccount.Balance);
            // This executes the commands.
            transactionManager.ProcessPendingTransactions();
            Assert.IsFalse(transactionManager.HasPendingTransactions);
            Assert.AreEqual(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.IsFalse(transactionManager.HasPendingTransactions);
            Assert.AreEqual(50, suesAccount.Balance);
        }
        [TestMethod]
        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.IsTrue(transactionManager.HasPendingTransactions);
            Assert.AreEqual(150, bobsAccount.Balance);
            // The pending transactions (the withdrawal of 100), should execute now.
            transactionManager.ProcessPendingTransactions();
            Assert.IsFalse(transactionManager.HasPendingTransactions);
            Assert.AreEqual(50, bobsAccount.Balance);
        }
        [TestMethod]
        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.AreEqual(250, checking.Balance);
            Assert.AreEqual(850, savings.Balance);
        }
    }
}

 

The three test methods simulate what our client program might do, and verify that the Invoker executes the Commands – or not, if the Command should not complete.

 

Enhancement, with the ability to “Undo” a Command

I was asked how to implement the undo, and manage the Invoker (run it on a schedule and delete old Commands).

The short answer to adding the undo ability is to add an undo to the ITransaction interface, like this:

public interface ITransaction
{
    bool IsCompleted { get; set; }
    void Execute();
    void Undo();
}

Then, add an Undo function to each Command class. The Undo code for Deposit would look like the Execute code for Withdraw, and vice versa. The Undo for Transfer would reverse the accounts.

However, this feature request leads to more questions.

Determining all the requirements

Will we want to undo all Commands, or only selected Commands? If it’s only selected Commands, how will we know which ones to undo? We’ll probably need a unique ID for each Command object, which means we need to define an ID property in ITransaction.

We’ll also want to only undo Commands that were successfully completed. If we undo a Command, does that mean we can run Execute on it again? Or, should we only be able to delete it? Do we need another Boolean property, to show if the Undo was completed? Maybe we need a CommandStatus enumerator, with values for all possible states of the Command object.

What criteria should we use to delete a Command from the TransactionManager object? If we want to call the Undo function on some Commands, we can’t delete the Command objects after the Execute is successful.

By the way, figuring out questions like these is a large part of how I spend my day programming. It’s a lot more thinking, questioning and planning, than typing.

 

Answers for the requirement questions

For this example, we’ll say:

  • We want to Undo a Command based on an ID.
  • You can only Undo a Command if the Execute was completed.
  • If the Undo fails, set the Command’s status to UndoFailed – so we can retry the Undo again.
  • If the Undo succeeds, set the Command’s status to UndoSucceeded – so we can delete it. We do not want to allow the user to call Execute again, after an Undo.
  • We will delete all completed Commands over 15 days old. So, we also need a date property in ITransaction.

 

Updated code, with undo features

Here is the new code, with Undo capabilities added. I wrote this quickly, so please tell me if you see any errors.

 

Enums.cs

namespace Engine.CommandPattern.PatternVersion_WithUndo
{
    public enum CommandState
    {
        Unprocessed,
        ExecuteFailed,
        ExecuteSucceeded,
        UndoFailed,
        UndoSucceeded
    }
}

This is for all the different possible states of a Command object.

 

ITransaction.cs

using System;
namespace Engine.CommandPattern.PatternVersion_WithUndo
{
    public interface ITransaction
    {
        int ID { get; set; }
        DateTime CreatedOn { get; set; }
        CommandState Status { get; set; }
        void Execute();
        void Undo();
    }
}

We have the new properties, and the Undo function, declared in the interface now. The Status property replaces the old Boolean IsCompleted property.

 

Deposit.cs

using System;
namespace Engine.CommandPattern.PatternVersion_WithUndo
{
    public class Deposit : ITransaction
    {
        private readonly Account _account;
        private readonly decimal _amount;
        public int ID { get; set; }
        public DateTime CreatedOn { get; set; }
        public CommandState Status { get; set; }
        public Deposit(int id, Account account, decimal amount)
        {
            ID = id;
            CreatedOn = DateTime.UtcNow;
            _account = account;
            _amount = amount;
            Status = CommandState.Unprocessed;
        }
        public void Execute()
        {
            _account.Balance += _amount;
            Status = CommandState.ExecuteSucceeded;
        }
        public void Undo()
        {
            if(_account.Balance >= _amount)
            {
                _account.Balance -= _amount;
                Status = CommandState.UndoSucceeded;
            }
            else
            {
                Status = CommandState.UndoFailed;
            }
        }
    }
}

 

Withdraw.cs

using System;
namespace Engine.CommandPattern.PatternVersion_WithUndo
{
    public class Withdraw : ITransaction
    {
        private readonly Account _account;
        private readonly decimal _amount;
        public int ID { get; set; }
        public DateTime CreatedOn { get; set; }
        public CommandState Status { get; set; }
        public Withdraw(int id, Account account, decimal amount)
        {
            ID = id;
            CreatedOn = DateTime.UtcNow;
            _account = account;
            _amount = amount;
            Status = CommandState.Unprocessed;
        }
        public void Execute()
        {
            if(_account.Balance >= _amount)
            {
                _account.Balance -= _amount;
                Status = CommandState.ExecuteSucceeded;
            }
            else
            {
                Status = CommandState.ExecuteFailed;
            }
        }
        public void Undo()
        {
            _account.Balance += _amount;
            Status = CommandState.UndoSucceeded;
        }
    }
}

Transfer.cs

using System;
namespace Engine.CommandPattern.PatternVersion_WithUndo
{
    public class Transfer : ITransaction
    {
        private readonly decimal _amount;
        private readonly Account _fromAccount;
        private readonly Account _toAccount;
        public int ID { get; set; }
        public DateTime CreatedOn { get; set; }
        public CommandState Status { get; set; }
        public Transfer(int id, Account fromAccount, Account toAccount, decimal amount)
        {
            ID = id;
            CreatedOn = DateTime.UtcNow;
            _fromAccount = fromAccount;
            _toAccount = toAccount;
            _amount = amount;
            Status = CommandState.Unprocessed;
        }
        public void Execute()
        {
            if(_fromAccount.Balance >= _amount)
            {
                _fromAccount.Balance -= _amount;
                _toAccount.Balance += _amount;
                Status = CommandState.ExecuteSucceeded;
            }
            else
            {
                Status = CommandState.ExecuteFailed;
            }
        }
        public void Undo()
        {
            // Remove the money from the original "to" account, 
            // and add it back to the original "from" account.
            if(_toAccount.Balance >= _amount)
            {
                _toAccount.Balance -= _amount;
                _fromAccount.Balance += _amount;
                Status = CommandState.UndoSucceeded;
            }
            else
            {
                Status = CommandState.UndoFailed;
            }
        }
    }
}

The Command class constructors accept a parameter for the ID property, set the CreatedOn property to the DateTime the object was instantiated (using UTC time, which is a good idea for any DateTime that might be stored somewhere – like a database or message queue), and set the initial Status to Unprocessed.

The Execute and Undo functions set the Status for all possible results, so we know if the Execute/Undo succeeded or failed.

 

TransactionManager.cs

using System;
using System.Collections.Generic;
using System.Linq;
namespace Engine.CommandPattern.PatternVersion_WithUndo
{
    public class TransactionManager
    {
        private readonly List<ITransaction> _transactions = new List<ITransaction>();
        public bool HasPendingTransactions
        {
            get
            {
                return _transactions.Any(x =>
                    x.Status == CommandState.Unprocessed ||
                    x.Status == CommandState.ExecuteFailed ||
                    x.Status == CommandState.UndoFailed);
            }
        }
        public void AddTransaction(ITransaction transaction)
        {
            _transactions.Add(transaction);
        }
        public void ProcessPendingTransactions()
        {
            // Execute transactions that are unprocessed, or had a previous Execute fail.
            foreach(ITransaction transaction in _transactions.Where(x =>
                x.Status == CommandState.Unprocessed ||
                x.Status == CommandState.ExecuteFailed))
            {
                transaction.Execute();
            }
            // Retry the Undo, for transactions that had a previous Undo fail.
            foreach(ITransaction transaction in _transactions.Where(x =>
                x.Status == CommandState.UndoFailed))
            {
                transaction.Undo();
            }
        }
        public void UndoTransactionNumber(int id)
        {
            // Get the Command object that has the passed ID.
            // If it does not exist in _transactions, the result will be null (default, for this object type).
            ITransaction transaction = _transactions.FirstOrDefault(x => x.ID == id);
            if(transaction == null)
            {
                throw new ArgumentException(string.Format("There is no transaction number: {0}", id));
            }
            if(transaction.Status != CommandState.ExecuteSucceeded)
            {
                throw new ArgumentException("Can only undo transactions that have been successfully executed.");
            }
            // We have a valid transaction, so perform the "undo".
            transaction.Undo();
            // Remove the transaction, if it was successfully completed.
            if(transaction.Status == CommandState.UndoSucceeded)
            {
                _transactions.Remove(transaction);
            }
        }
        public void RemoveOldTransactions()
        {
            // Remove transaction that have been Executed, and are more than 15 days old.
            _transactions.RemoveAll(x =>
                x.Status == CommandState.ExecuteSucceeded &amp;&amp;
                (DateTime.UtcNow - x.CreatedOn).Days > 15);
        }
    }
}

Now that we have more possible states for the Command objects, the functions need to make sure they only use Commands that are in the appropriate state.

HasPendingTransaction property has been changed to look for Command objects that still need to be processed – because they are new, or an Execute or Undo failed.

ProcessPendingTransdactions has been changed to call Execute, for transactions that are unprocessed or where Execute failed. Then, it calls Undo on transactions where a previous call to Undo failed.

UndoTransactionNumber will call Undo on the appropriate Command object, if it is still in the _transactions list, and it has been successfully executed. If the Undo succeeds, the Command object is removed from the transactions list.

RemoveOldTransactions is a new method to delete the Command objects that successfully execute, and are more than 15 days old.

NOTE: We could have moved throwing an exception if you try to undo a Command object where the status is not ExecuteSucceeded into the Undo functions.

 

TestCommandPattern.cs

using Engine.CommandPattern.PatternVersion_WithUndo;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace TestEngine.CommandPattern.PatternVersion_WithUndo
{
    [TestClass]
    public class TestCommandPattern
    {
        [TestMethod]
        public void Test_AllTransactionsSuccessful()
        {
            TransactionManager transactionManager = new TransactionManager();
            Account suesAccount = new Account("Sue Smith", 0);
            Deposit deposit = new Deposit(1, suesAccount, 100);
            transactionManager.AddTransaction(deposit);
            // Command has been added to the queue, but not executed.
            Assert.IsTrue(transactionManager.HasPendingTransactions);
            Assert.AreEqual(0, suesAccount.Balance);
            // This executes the commands.
            transactionManager.ProcessPendingTransactions();
            Assert.IsFalse(transactionManager.HasPendingTransactions);
            Assert.AreEqual(100, suesAccount.Balance);
            // Add a withdrawal, apply it, and verify the balance changed.
            Withdraw withdrawal = new Withdraw(2, suesAccount, 50);
            transactionManager.AddTransaction(withdrawal);
            transactionManager.ProcessPendingTransactions();
            Assert.IsFalse(transactionManager.HasPendingTransactions);
            Assert.AreEqual(50, suesAccount.Balance);
            // Test the undo
            transactionManager.UndoTransactionNumber(2);
            Assert.IsFalse(transactionManager.HasPendingTransactions);
            Assert.AreEqual(100, suesAccount.Balance);
        }
        [TestMethod]
        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(1, bobsAccount, 100));
            transactionManager.AddTransaction(new Deposit(2, 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.IsTrue(transactionManager.HasPendingTransactions);
            Assert.AreEqual(150, bobsAccount.Balance);
            // The pending transactions (the withdrawal of 100), should execute now.
            transactionManager.ProcessPendingTransactions();
            Assert.IsFalse(transactionManager.HasPendingTransactions);
            Assert.AreEqual(50, bobsAccount.Balance);
            // Test the undo
            transactionManager.UndoTransactionNumber(2);
            // The undo failed, because there is not enough money in the account to undo a deposit of 75
            Assert.IsTrue(transactionManager.HasPendingTransactions);
            Assert.AreEqual(50, bobsAccount.Balance);
            transactionManager.UndoTransactionNumber(1);
            // The previous undo (for transaction ID 2) is still pending.
            // But, we successfully undid transaction ID 1.
            Assert.IsTrue(transactionManager.HasPendingTransactions);
            Assert.AreEqual(150, bobsAccount.Balance);
            // This should re-do the failed undo for transaction ID 2
            transactionManager.ProcessPendingTransactions();
            Assert.IsFalse(transactionManager.HasPendingTransactions);
            Assert.AreEqual(75, bobsAccount.Balance);
        }
        [TestMethod]
        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(1, checking, savings, 750));
            transactionManager.ProcessPendingTransactions();
            Assert.AreEqual(250, checking.Balance);
            Assert.AreEqual(850, savings.Balance);
            // Undo the transfer, and check the account balances.
            transactionManager.UndoTransactionNumber(1);
            Assert.IsFalse(transactionManager.HasPendingTransactions);
            Assert.AreEqual(1000, checking.Balance);
            Assert.AreEqual(100, savings.Balance);
        }
    }
}

I expanded the unit tests to also check the Undo function.

 

Managing the Invoker (TransactionManager)

Normally, I use this pattern with a program that runs on a schedule – once a night, or every few hours.

To do that, I create a Windows Service solution. The service is installed on a server in a network data center – so it should always be running.

The service would have a Timer object, that knows the time of day, or interval, to call TransactionManager.ProcessPendingTransactions.

I would also put the retry rules into there – if needed. For example, if HasPendingTransactions is true, after running ProcessPendingTransaction, wait 15 minutes and re-run ProcessPendingTransactions.

You could also add a Timer to call the RemoveOldTransactions once a night. Or, however often you wanted it to run.

 

Where I’ve found the Command Design Pattern useful

I often use the Command Design Pattern with a message queue application. We’ll use the message queue for logging, and to save our commands to disk. If we re-boot the computer (or if it crashes), our program can read the incomplete commands from the message queue, and continue working – without losing any data.

I’ve normally 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.

You normally don’t need to use the Command design pattern for personal projects. It makes the program unnecessarily complex. However, it can be useful for a large business application that needs to be extremely reliable.

All my design pattern lessons

Source code for my design pattern lessons

24 Comments

  1. Brendan
    Brendan October 5, 2016

    Thanks for this! It was certainly helpful! What about when the command is an asynchronous operation that returns some result that needs to then be parsed in some intelligent way. Would the receiver then be the handler for that command?

    • Scott Lilly
      Scott Lilly October 6, 2016

      You’re welcome!

      There are a few different ways you could do this, depending on exactly what you need the program to do.

      My first thought is to make the Invoker function asynchronous, by adding this to the Invoker (TransactionManager) class, for example:
      using System.Threading.Tasks;

      public Task ExecuteTransactionAndReturnSuccess(ITransaction transaction)
      {
      return Task.Run(() =>
      {
      _transactions.Add(transaction);

      transaction.Execute();

      return transaction.IsCompleted;
      });
      }

      Implementing that, in the calling code (in this case, the TestCommandPattern class), would look like:
      [TestMethod]
      public void Test_Async()
      {
      TransactionManager transactionManager = new TransactionManager();
      Account checking = new Account("Mike Brown", 1000);
      bool result = transactionManager.ExecuteTransactionAndReturnSuccess(new Withdraw(checking, 750)).Result;

      Assert.IsTrue(result);
      Assert.AreEqual(250, checking.Balance);
      }

      If the asynchronous code is part of the Command class’ Execute function, and you want to use an eventhandler for when the async code is complete (I’m guessing that’s what you mean, when asking if the receiver will be the handler for that command), I think the handler would probably need to be in the Invoker. When the Invoker function receives a Command, it set the eventhandler on the Command object, executes the Command code, then waits for the event to be raised by the Command object.

      If I misunderstood that, or if you still have questions, please let me know. If you have a code sample you could post someplace (like https://gist.github.com/), I might be able to give a more exact answer.

  2. Golzari
    Golzari October 27, 2016

    Thanks a lot

  3. Lukasz
    Lukasz February 26, 2017

    Wow, Sir, your examples are the best. It would be great if you could provide such a great examples for other design patterns.

    Well done!

    • Scott Lilly
      Scott Lilly February 26, 2017

      Thank you! I realized these lessons don’t have a link to the page with the other lessons. Here are some more design pattern lessons. I plan to make more, and am currently working on an MVVM example.

  4. shawn
    shawn June 13, 2017

    Scott,

    This really helped me wrap my mind around command pattern.

    Thanks

    • Scott Lilly
      Scott Lilly June 13, 2017

      You’re welcome. It’s an interesting technique, although it often takes some time to reach the “Aha!” moment, when it’s clear.

  5. Dimitar Denchev
    Dimitar Denchev November 21, 2017

    Great article – I will go throgh the other patterns from your link (https://www.scottlilly.com/c-design-patterns/) – very well written and explained and the examples are real examples from your experience, greatly appreciated!

  6. Andre
    Andre December 15, 2017

    Wow, this is the best example for the command pattern i’ve ever seen. Thank you kindly!

  7. M.
    M. February 6, 2018

    Hello Mr. Scott,

    If I want to save the result to database. How should be the implementation on Command Pattern?

    • Scott Lilly
      Scott Lilly February 7, 2018

      Hello,

      Do you want to have each command object save something to the database? If so, you could put the code for that in the Execute() function. Or, are you trying to do something else?

  8. Arctur Gray
    Arctur Gray February 25, 2018

    Thanks for your tutorials. They are really nice.
    I think it’s a good idea to make something like ExecutionUnit or whatever, add commands to this unit, and then give it to the manager, so that if one command from the unit fails, the whole unit is not applied (basically, all previously successfully executed commands in the unit are undone in the reverse order, and all following commands are not executed at all). Just a thought.

    • Scott Lilly
      Scott Lilly February 25, 2018

      You’re welcome. You could definitely combine the Command Pattern with the “Unit of Work” pattern, to give you the ability to rollback transactions.

  9. Ivan N.
    Ivan N. April 16, 2018

    Best explanation of the pattern i’ve found. Thanks a lot!

  10. Jorge Olivé
    Jorge Olivé September 10, 2018

    Beautifully explained. Thank you!

  11. Milan_Serbia
    Milan_Serbia January 16, 2019

    M8, your are awesome. Beautiful explainations, as always.

  12. Karol
    Karol March 22, 2022

    Very valuable erticle. Thank you for your time and efforts you spent making this article.

Leave a Reply

Your email address will not be published. Required fields are marked *