Command Pattern in C# – Transform Requests into Objects for Flexible Management
Learn Command Pattern – a design pattern that encapsulates requests as standalone objects, enabling undo/redo, queuing, and transaction management in object-oriented programming with C#.
Table of Contents
No table of contents available for this article
1. Introduction to Command Pattern
Command (also known as Action or Transaction) is a design pattern belonging to the Behavioral Pattern group – patterns that focus on how objects communicate and distribute responsibilities among themselves.
The core idea of Command Pattern is to transform a request into a standalone object containing all information about that request. This "encapsulation" allows you to:
Parameterize methods with different requests
Build queue systems
Support undo/redo functionality
Manage transactions and logging
To visualize this, think of a Command Object as an "envelope" containing complete information about a command: who sends it, who receives it, what the content is, and how to execute it.
An intuitive understanding: The word "Command" means "to give orders." In the military, a Commander doesn't do the work directly – they give orders. So there must be:
The one who gives orders (Invoker) – encapsulates commands into a class
The one who receives orders (Receiver) – distinguishes and executes the correct command
Usage frequency: Quite high in real-world applications
2. Why Was Command Pattern Created?
In Object-Oriented Programming (OOP), there are situations where we need to send requests to Objects without knowing beforehand:
Which Object will receive and process the request?
How exactly will it be processed?
Real-world example: You're building a text editor application (like Microsoft Word). When users click the Undo, Redo, or Save button, the request is sent to the processing system. But at design time, you can't know exactly which object will handle it – it could be Document, TextFormatter, or FileManager.
Command Pattern was created to solve this problem, helping us:
Avoid hard-wired connections: Directly coupling a class to a specific request makes code inflexible and hard to extend
Reduce dependency: Object classes shouldn't depend rigidly on any specific request
Separate "sender" from "receiver": Send requests without knowing who receives them or how they're processed
3. Command Pattern Architecture
Command Pattern consists of 5 main components:
Command: Interface or abstract class defining the Execute() method. All requests are encapsulated as Commands.
ConcreteCommand: Classes implementing Command. Links a Receiver to a specific action. When Execute() is called, it invokes the corresponding operation on the Receiver.
Client: Receives requests from users, packages them into appropriate ConcreteCommands, and sets up the Receiver.
Invoker: Receives ConcreteCommand from Client and calls Execute() to run the request. Invoker doesn't know processing details – it just invokes commands.
Receiver: The component that actually handles business logic. In ConcreteCommand's Execute(), the appropriate Receiver method is called.
Workflow:
Client → creates ConcreteCommand (attached to Receiver)
→ passes to Invoker
→ Invoker calls Execute()
→ ConcreteCommand calls method on Receiver
→ Receiver executes actual logic4. Advantages and Disadvantages
Advantages:
Single Responsibility Principle: Clear separation between the class invoking operations and the class executing them
Open/Closed Principle: Easy to add new Commands without modifying existing code
Undo/Redo Support: Each Command can store state and reverse actions
Reduced coupling: Invoker and Receiver don't know about each other, communicating through Commands
Flexible data transfer: Requests encapsulated as objects are easy to store, serialize, and transmit over networks
Disadvantages:
Increased complexity: Creates many new classes (one per command), potentially bloating the codebase
Overhead: For simple operations, creating Command objects may be overkill
5. When Should You Use Command Pattern?
Consider using Command Pattern when:
You need to parameterize actions: Turn actions into parameters, pass them through methods
You need delayed execution: Create requests now, execute them later
You need undo/redo: Store command history for reverting or replaying
You need logging/callbacks/transactions: Log operations, handle callbacks, or manage transactions
You need to queue commands: Queue commands and execute them in order
6. C# Implementation Example
Let's build a fan control system using a remote. This is a classic example for understanding Command Pattern.
Step 1: Create the Receiver (Fan – the object doing the actual work)
csharp
class Fan
{
public void TurnOn()
{
Console.WriteLine("Fan is now ON");
}
public void TurnOff()
{
Console.WriteLine("Fan is now OFF");
}
}Step 2: Define the Command Interface
csharp
interface ICommand
{
void Execute();
void Undo();
}Step 3: Create ConcreteCommands
csharp
class TurnOnCommand : ICommand
{
private readonly Fan fan;
public TurnOnCommand(Fan fan)
{
this.fan = fan;
}
public void Execute()
{
fan.TurnOn();
}
public void Undo()
{
fan.TurnOff();
}
}
class TurnOffCommand : ICommand
{
private readonly Fan fan;
public TurnOffCommand(Fan fan)
{
this.fan = fan;
}
public void Execute()
{
fan.TurnOff();
}
public void Undo()
{
fan.TurnOn();
}
}Step 4: Create the Invoker (Remote control)
csharp
class Remote
{
private readonly ICommand turnOnCommand;
private readonly ICommand turnOffCommand;
public Remote(ICommand turnOnCommand, ICommand turnOffCommand)
{
this.turnOnCommand = turnOnCommand;
this.turnOffCommand = turnOffCommand;
}
public void TurnOnButtonClick()
{
turnOnCommand.Execute();
}
public void TurnOffButtonClick()
{
turnOffCommand.Execute();
}
}Step 5: Client Usage
csharp
class Client
{
static void Main(string[] args)
{
Fan fan = new Fan();
ICommand turnOnCommand = new TurnOnCommand(fan);
ICommand turnOffCommand = new TurnOffCommand(fan);
Remote remote = new Remote(turnOnCommand, turnOffCommand);
remote.TurnOnButtonClick();
remote.TurnOffButtonClick();
}
}Benefits of this design:
Remote (Invoker) knows nothing about Fan (Receiver)
Want to control different devices (lights, AC)? Just create new Commands!
Want to add Undo? The Undo() method is already available in each Command
7. Related Design Patterns
Command Pattern is often compared with other patterns for connecting senders and receivers:
Chain of Responsibility: Sends request along a chain of processors until one handles it. Relationship: 1 → 0..n
Command: Unidirectional connection between sender and receiver through an intermediary layer. Relationship: 1 → 1
Mediator: Removes direct connections, forcing communication through a mediator object. Relationship: n → n
Observer: Defines an interface allowing multiple receivers to subscribe/unsubscribe to requests. Relationship: 1 → n
If you found this article helpful, explore more in the Design Patterns Series to enhance your programming skills!
References
[1] Refactoring.Guru. https://refactoring.guru/design-patterns
[2] Design Patterns for Dummies, Steve Holzner, PhD
[3] Head First Design Patterns, Eric Freeman
[4] Gang of Four Design Patterns 4.0
[5] Dive into Design Patterns