The Command pattern is a behavioral design pattern that we can use to turn a request into an object that contains all the information about the request.
The Command design pattern is quite popular in C#, especially when we want to delay or queue a request’s execution or when we want to keep track of our operations. Furthermore, this possibility to keep track of our operations gives us the opportunity to undo them as well.
For the main page of this series check out C# Design Patterns.
VIDEO: Command Design Pattern in C#.
Implementation of Command Design Pattern
The Command design pattern consists of the Invoker class, Command class/interface, Concrete command classes, and the Receiver class. Having that in mind, in our example, we are going to follow the same design structure.
So, what we are going to do is write a simple app in which we are going to modify the price of the product that will implement the Command design pattern.
That being said, let’s start with the Product
receiver class, which should contain the base business logic in our app:
public class Product { public string Name { get; set; } public int Price { get; set; } public Product(string name, int price) { Name = name; Price = price; } public void IncreasePrice(int amount) { Price += amount; Console.WriteLine($"The price for the {Name} has been increased by {amount}$."); } public void DecreasePrice(int amount) { if(amount < Price) { Price -= amount; Console.WriteLine($"The price for the {Name} has been decreased by {amount}$."); } } public override string ToString() => $"Current price for the {Name} product is {Price}$."; }
So this is our receiver class, which has a pretty straightforward logic. We just increase or conditionally decrease the product’s price. Finally, we have the ToString
method, to be able to print our object.
Now the Client class can instantiate the Product
class and execute the required actions. But the Command design pattern states that we shouldn’t use receiver classes directly. Instead, we should extract all the request details into a special class – Command.
And that is exactly what we are going to do.
The first thing we are going to do is to add the ICommand
interface:
public interface ICommand { void ExecuteAction(); }
Just to enumerate our price modification actions, we are going to add a simple PriceAction
enumeration:
public enum PriceAction { Increase, Decrease }
Finally, let’s add the ProductCommand
class:
public class ProductCommand : ICommand { private readonly Product _product; private readonly PriceAction _priceAction; private readonly int _amount; public ProductCommand(Product product, PriceAction priceAction, int amount) { _product = product; _priceAction = priceAction; _amount = amount; } public void ExecuteAction() { if(_priceAction == PriceAction.Increase) { _product.IncreasePrice(_amount); } else { DecreasePrice(_amount); } } }
As we can see, the ProductCommand
class has all the information about the request and based on that executes required action.
To continue on, let’s add the ModifyPrice
class, which will act as Invoker:
public class ModifyPrice { private readonly List<ICommand> _commands; private ICommand _command; public ModifyPrice() { _commands = new List<ICommand>(); } public void SetCommand(ICommand command) => _command = command; public void Invoke() { _commands.Add(_command); _command.ExecuteAction(); } }
This class can work with any command that implements the ICommand
interface and store all the operations as well.
Now, we can start working with the client part:
class Program { static void Main(string[] args) { var modifyPrice = new ModifyPrice(); var product = new Product("Phone", 500); Execute(product, modifyPrice, new ProductCommand(product, PriceAction.Increase, 100)); Execute(product, modifyPrice, new ProductCommand(product, PriceAction.Increase, 50)); Execute(product, modifyPrice, new ProductCommand(product, PriceAction.Decrease, 25)); Console.WriteLine(product); } private static void Execute(Product product, ModifyPrice modifyPrice, ICommand productCommand) { modifyPrice.SetCommand(productCommand); modifyPrice.Invoke(); } }
This should be our result:
Excellent. We can see the order of our actions and the correct price after modifications.
Having in mind that we are keeping track of our actions in the Invoker class, we can use that to Undo our operations if we want to.
So, let’s try that.
Implementing Undo Operation in the Command Design Pattern
To implement the Undo operation let’s start with the ICommand
interface modification:
public interface ICommand { void ExecuteAction(); void UndoAction(); }
Then, let’s modify the ProductCommand
class by adding the UndoAction
method:
public void UndoAction() { if (_priceAction == PriceAction.Increase) { _product.DecreasePrice(_amount); } else { _product.IncreasePrice(_amount); } }
Of course, we have to modify the ModifyPrice
class as well by adding the UndoActions
method:
public void UndoActions() { foreach (var command in Enumerable.Reverse(_commands)) { command.UndoAction(); } }
Please note that we are not using the Linq Reverse method but the Enumerable.Reverse()
instead. That’s because the Linq method would mutate our list and we don’t want that. All we want is just a reversed list but without mutating the original list itself.
Now, when the Client class calls the UndoActions
method, it will iterate through all the operations from the list and execute the opposite operation from the previously required.
Let’s try that:
class Program { static void Main(string[] args) { var modifyPrice = new ModifyPrice(); var product = new Product("Phone", 500); Execute(product, modifyPrice, new ProductCommand(product, PriceAction.Increase, 100)); Execute(product, modifyPrice, new ProductCommand(product, PriceAction.Increase, 50)); Execute(product, modifyPrice, new ProductCommand(product, PriceAction.Decrease, 25)); Console.WriteLine(product); Console.WriteLine(); modifyPrice.UndoActions(); Console.WriteLine(product); } private static void Execute(Product product, ModifyPrice modifyPrice, ICommand productCommand) { modifyPrice.SetCommand(productCommand); modifyPrice.Invoke(); } }
The result:
Everything works as it supposed to.
Improving the Solution
We have implemented the Command design pattern into our app, and there is nothing wrong with that. But there is one flaw in our solution which is not related to the Command pattern but to the business logic overall.
If we were to change the decreased amount from 25 to 2500 for example, what would happen? Well, we have the protection for that in the DecreasePrice
method and it will not affect the result, and you are right. But it is going to affect the Undo actions.
Let’s see how:
... Execute(product, modifyPrice, new ProductCommand(product, PriceAction.Decrease, 2500)); ...
This is going to be the result:
As you can see, our price hasn’t been decreased but the operation has been preserved in our store which caused the Undo action to fail.
So, let’s fix that.
The first thing we are going to do is to modify the DecreasePrice
method in the Product
class:
public bool DecreasePrice(int amount) { if(amount < Price) { Price -= amount; Console.WriteLine($"The price for the {Name} has been decreased by {amount}$."); return true; } return false; }
Now, we can modify the ProductCommand
class as well:
public class ProductCommand : ICommand { private readonly Product _product; private readonly PriceAction _priceAction; private readonly int _amount; public bool IsCommandExecuted { get; private set; } public ProductCommand(Product product, PriceAction priceAction, int amount) { _product = product; _priceAction = priceAction; _amount = amount; } public void ExecuteAction() { if(_priceAction == PriceAction.Increase) { _product.IncreasePrice(_amount); IsCommandExecuted = true; } else { IsCommandExecuted = _product.DecreasePrice(_amount); } } public void UndoAction() { if (!IsCommandExecuted) return; if (_priceAction == PriceAction.Increase) { _product.DecreasePrice(_amount); } else { _product.IncreasePrice(_amount); } } }
And that is it. Now if we start our app with a decreased amount of 2500 the result would be correct:
Great job.
Conclusion
Even though the Command design pattern introduces complexity to our code, it can be very useful.
With it, we can decouple classes that invoke operations from classes that perform these operations. Additionally, if we want to introduce new commands, we don’t have to modify existing classes. Instead, we can just add those new command classes to our project.
hi
thanks
typo: DecreasePrice(_amount);
Hello Efim. You are most welcome. Now, I must be blind here, so could you please tell me where is the typo?
private static void Execute(Product product, ModifyPrice modifyPrice, ICommand roductCommand
{
modifyPrice.SetCommand(productCommand);
modifyPrice.Invoke();
}
We may remove product pass in parameter?
Yeah, you are right.
Great & neat. thanks
You are most welcome.
Great article!
As a suggestion, What do you think instead of creating IProductCommand you create two Command classes.
The IncreasePriceCommand and DecreasePriceCommand fit better to command idea as well you can remove IFs and enum PriceAction.
Does it make sense for you?
Very good example thank you very much
Thank you a lot. I hope you will enjoy other articles as well. All the best.
Nice explanation. Thanks! I’ve got this in my favorites and will reference over the coming months as I force myself to take a more clean / stable and scalable approach!
My brain did throw an unknown type error on “IProductCommand” just FYI. Also, you could make this a lot better by attaching a diagram which matches the class names etc. However, sometimes diagrams cause us to think about something incorrectly / get the wrong idea and it’s best to just dive straight into the example – which is why I’m here vs. just looking at other’s diagrams! 🙂
Hello James. Thank you for reading this article and for your comment as well. About your error, all I can say is to try to download our source code and compare it with yours. Best regards.
It’s an error in your article not source code. IProductCommand should be ICommand. No biggie. Thanks again!
Would you believe me if I say that I wanted to ask you: “Why IProductCommand, there is no IProductCommand interface?”… But gues what, there it is 😀 😀 Thank you for your suggestion, and sorry for the error, really my bad.
Great explanation.Admirable example. Thank you !