At its core, the command pattern is a behavioral software design pattern that helps separate the logic of a request’s execution from its actual implementation. This separation helps to promote greater flexibility and code organization in larger codebases. In this article, I’ll go over the command pattern in C# so we can see it in action!
Maintaining well-organized code is crucial for any software engineer, and understanding design patterns — such as the command pattern — is an essential part of this process. Together we’ll implement the command pattern in C# and you can see if this pattern will help in your current codebase.
Let’s dive in!
The Command Pattern is a behavioral design pattern that decouples the sender of a request from the object that performs the action requested. This software pattern encloses a request as an object, thereby allowing you to parameterize clients with different requests. The object that encapsulates the command may know how to invoke the action, but it doesn’t necessarily know the receiver of the request.
In C#, the Command Pattern is used to encapsulate requests as objects, which is particularly helpful in constructing composite commands or transaction processing. The pattern is designed to enable the separation of application-specific functionality from the underlying system, ensuring that changes made to one do not affect the other.
By separating commands from the classes that use them, the command pattern allows you to create a more cohesive and maintainable codebase over time. This separation enables the code to scale and evolve over time without much impact on the system’s overall architecture.
Let’s say we have a fictional restaurant application where a customer can order a beverage. We would use the Command Pattern to encapsulate the request for the beverage order and its processing. The customer would create a concrete command object, specifying a drink order. The Invoker object, the waiter, sends the Concrete Command object to the receiver, the bartender, which then executes the request.
This separation of roles enables the code to scale and evolve over time while maintaining code modularity and cohesion — At least, that’s the theory behind following a pattern like this. In practice, we may find that the levels of abstraction and separation over-complicate, so it’s important to remain pragmatic and choose what works situationally.
Here’s an example of implementing the Command Pattern in C#:
// Command
public interface ICommand
{
void Execute();
}
// Concrete Command
public class BeverageOrderCommand : ICommand
{
private readonly Beverage _beverage;
private readonly Bartender _bartender;
public BeverageOrderCommand(Beverage beverage, Bartender bartender)
{
_beverage = beverage;
_bartender = bartender;
}
public void Execute()
{
_bartender.ExecuteDrinkOrder(_beverage);
}
}
// Receiver
public class Bartender
{
public void ExecuteDrinkOrder(Beverage beverage)
{
Console.WriteLine($"Preparing {beverage.Name}");
}
}
// Invoker
public class Waiter
{
private readonly ICommand _command;
public Waiter(ICommand command)
{
_command = command;
}
public void OrderDrink()
{
_command.Execute();
}
}
We’ll revisit how to put this all together in a later section, so keep on reading!
The Command Pattern follows several principles of object-oriented design. Two of the most relevant principles are the Open-Closed principle and the Single Responsibility principle.
The Open-Closed principle states that software entities should be open for extension but closed for modification. In other words, once a class is defined and implemented, it should not be changed, except for maintenance purposes. Instead, new features should be added by creating new classes that inherit from or interface with the existing class.
When applied to the Command Pattern, the Open-Closed principle emphasizes the importance of creating concrete commands that can be extended over time, without modifying the existing code. This principle enables us to add new functionality to a command without changing the commands’ existing code.
The Single Responsibility principle states that every software entity should have only one responsibility. This means that a class should be designed in such a way that it only has one reason to change. In the context of the Command Pattern, the Command objects should have only one task to accomplish.
This can be helpful to recognize because if you have a module of your code that’s doing “too much” (i.e. not a single responsibility), you may be able to ask if some can be factored into a pattern like the Command Pattern. Functionally, it should remain the same — But the implementation and design may end up lending itself well to better organization of code.
The Command Pattern adheres to the SOLID principle, which is an acronym for five object-oriented design principles used in software development. Each letter represents a principle, as follows:
The Command Pattern is composed of four primary components:
Each component plays a critical role in encapsulating requests as objects in the Command Pattern. As we explore each of these, we can consider how the example we looked at relates.
The Invoker is responsible for invoking the commands and initiating their execution. It does not know the details of how the command is executed, unaware of the command’s receiver. The Invoker only works with the Command object’s generic interface, which is ICommand in C#. When the Invoker receives a command, it stores the command in a history of all invoked commands, providing support for undo and redo commands.
An example of an Invoker implementation in our C# example would be a Waiter class that executes a command by calling its execute() method. If we look at the implementation, we can see that the calls made inside of the Waiter don’t suggest any shared knowledge of the implementation of the commands.
The Command is the abstract encapsulation of a request, which includes the receiver object associated with this request. Commands expose an interface for invoking requests without coupling the sender’s implementation to the requester’s implementation. In other words, Command objects do not need to know anything about the object they report to – they merely execute the command’s logic and call upon the receiver object when necessary.
In our C# example, the Command object implements the ICommand interface, which serves as the basis for all commands. You could use a base class or an abstract class, but our example just uses the interface.
A Concrete Command extends Command and implements the execute() method in a way that defines a specific request and its receiver. Every concrete command corresponds to a specific operation on the receiver. This implementation is where the actual work is done. In other words, the concrete command is responsible for calling the appropriate receiver method(s) based on the request (the command).
An example of a Concrete Command implementation in our C# example is a BeverageOrderCommand, which implements the Execute() method to prepare the beverage.
The Receiver is responsible for providing the necessary actions for carrying out the request. These actions occur as a result of the Concrete Command call to the receiver’s execute() method. The receiver itself does not know anything about the command pattern – it only knows how to perform the action associated with the request that it received.
In our C# example, a Receiver implementation could be the class that defines the actual task to be executed, such as the Bartender class executing the beverage command.
Implementing the Command Pattern in C# involves a few fundamental steps, but when done correctly, it can lead to better organization and maintainability of your code. Here is a step-by-step guide to implementing the Command Pattern in C# along with code examples to help you understand how it all works.
Define the ICommand interface. This interface defines the operation that needs to be executed when the command is executed.
public interface ICommand
{
void Execute();
}
Create concrete classes that implement the ICommand interface. In this concrete command class, specific parameters and methods are implemented.
public class BeverageOrderCommand : ICommand
{
private readonly Beverage _beverage;
private readonly Bartender _bartender;
public BeverageOrderCommand(Beverage beverage, Bartender bartender)
{
_beverage = beverage;
_bartender = bartender;
}
public void Execute()
{
_bartender.ExecuteDrinkOrder(_beverage);
}
}
The Invoker class stores the ICommand object and calls its specific Execute() method to run the command. This class receives a command object to run in the form of ICommand.
public class Waiter
{
private readonly ICommand _command;
public Waiter(ICommand command)
{
_command = command;
}
public void OrderDrink()
{
_command.Execute();
}
}
This class holds the actual logic for performing the task. It receives the command sent from the client to execute an operation.
public class Bartender
{
public void ExecuteDrinkOrder(Beverage beverage)
{
Console.WriteLine($"Preparing {beverage.Name}");
}
}
The client creates the command objects and passes them to the Invoker class to execute the command.
class Program
{
static void Main(string[] args)
{
var bartender = new Bartender();
ICommand order = new BeverageOrderCommand(new Beverage("Margarita"), bartender);
Waiter waiter = new(order); waiter.OrderDrink();
}
}
By following these steps, you can effectively implement the Command Pattern in C#. The Command Pattern leads to cleaner and more modular code, which can help maintain the system’s structure over time.
The Command Pattern offers numerous benefits for software developers. By encapsulating requests as objects, you can effectively coordinate work on complex, multi-step processes. The pattern provides a uniform and consistent way to represent, store, and transmit requests. This helps to improve the overall structure and maintainability of your codebase.
Below are some benefits of the Command Pattern:
Although the Command Pattern offers plenty of benefits, it also has a few drawbacks:
A common misconception about the Command Pattern is that its use is limited to the undo/redo feature. While this is true, the Command Pattern offers much more than just undo and redo functionality. It promotes code maintainability, extensibility, and scalability, making it an essential part of any software developer’s toolkit.
By understanding the benefits and drawbacks of the Command Pattern, you can determine if it is the best pattern for your application. Like with any software development pattern, the Command Pattern has its advantages and disadvantages, so it’s important to know when and where it makes sense to use it.
The Command Pattern is a powerful and flexible design pattern that can assist software engineers in separating code execution from code triggering, increasing modularity and reusability. Applying the Command Pattern to C# code can help to organize large codebases, reduce complexity, promote high cohesion, and minimize coupling between objects. While there are some drawbacks to the pattern, the benefits far outweigh them in most cases.
Remember, the key components of the Command Pattern are the invoker, command, concrete command, and receiver. By following the SOLID principles, engineers can create clean and easy-to-maintain code. The challenge is to identify an appropriate context where the Command Pattern can be applied effectively.
By implementing the Command Pattern, software engineers can gain control over the execution of commands, encourage separation of concerns and improved code maintainability. If you’re interested in more learning opportunities, subscribe to my free weekly newsletter and !
Also published here.