Basics of Design Patterns — Command 🪖

Martin Jurran
Software Design Patterns
6 min readJun 17, 2024

The Command pattern creates an object that contains all the details of a request. This makes it easy to pass requests as arguments, execute actions, and also supports undoable operations.

The command pattern in action: Communicating with a satellite in space.

Real-World Analogy 🌍

Imagine you’re on vacation in Japan, attending a traditional tea ceremony. The ceremony involves many precise movements, each with a specific purpose.

Items used in the Japanese tea ceremony

The host acts as the invoker in this scenario, and the movements of the ceremony can be represented as individual commands. For example, there is a command for placing the tea bowl in front of a guest, a command for pouring hot water into the bowl, and a command for whisking the tea powder and hot water together.

Each of these commands can be encapsulated into standalone objects with all the necessary information about executing the command. This allows the host to delay or queue certain commands and even undo them if necessary.

Problem 🤔

Imagine you’re working on a music player application that allows users to create playlists and play songs. We have implemented a button component in the UI that can be used to perform various actions.

All buttons depend on the same class containing business logic

While all buttons look kind of similar, they are used to performing different actions. The easiest approach would be to create button subclasses for each possible action.

Tons of subclasses.. setting ourselves up for trouble

While this approach might look easy and good at first glance, it is flawed. We will end up with an enormous amount of subclasses, each containing crucial business logic.

Business logic is directly stored with UI components.

The whole program would break each time we edited the button base class. The user interface has become dependent on the button class and its associated business logic.

Solution 💡

Good software practice is to implement separation of concerns, which is a software design principle that helps to break down a software application into distinct layers. One example of this is dividing an application into a (1) user interface layer and a (2) business logic layer.

The user interface layer is responsible for displaying information on the screen, taking in user input, and showing the results of the app’s actions. But when it comes to important tasks, work is being delegated to the business logic layer.

Directly talking to the business layer (bad)

Rather than having the user interface send requests directly, the Command pattern separates the details of the request into a separate class called a command.

The command class contains all the information needed to execute a request, including the object being called, the method to be used, and the arguments. By triggering the command method, the request can be executed without the user interface object having to know any details of the business logic object handling the request.

Talking to business layer via command (good)

The next step would be to make all commands implement a joint interface. That is usually a single execution method without any parameters.

Buttons delegate work to respective commands

The interface allows for various commands to be used with the same sender without coupling it to a concrete command class. It also allows you to switch out commands linked to the sender during runtime.

Structure 🏗️

  1. Sender (class or object, also called invoker) is responsible for initiating requests. It needs to reference to the command object and trigger the command.
  2. Command Interface declares a single method for executing the command.
  3. Command Implementations implement requests and organize the calls to business logic.
  4. Receiver contains the business logic. Any class can act as a receiver.
  5. Client creates and configures the Command Implementations. The client must pass all necessary arguments to the commands.

How to implement

In this example, we have defined the CommandInterface, Receiver, SaveCommand, Sender, and Client classes in a single file. The Main method in the Client class creates instances of the Receiver, SaveCommand, and Sender classes, and executes the SaveCommand by setting it as the command for the Sender class to execute. When the command is executed, it calls the SaveData method on the Receiver class, which prints “Data saved successfully” to the console.

using System;

public interface CommandInterface
{
void Execute();
}

public class Receiver
{
public void SaveData()
{
Console.WriteLine("Data saved successfully");
}
}

public class SaveCommand : CommandInterface
{
private Receiver _receiver;

public SaveCommand(Receiver receiver)
{
_receiver = receiver;
}

public void Execute()
{
_receiver.SaveData();
}
}

public class Sender
{
private CommandInterface _command;

public void SetCommand(CommandInterface command)
{
_command = command;
}

public void ExecuteCommand()
{
_command.Execute();
}
}

class Client
{
static void Main(string[] args)
{
Receiver receiver = new Receiver();
SaveCommand saveCommand = new SaveCommand(receiver);
Sender sender = new Sender();
sender.SetCommand(saveCommand);
sender.ExecuteCommand();
}
}

Pros and Cons

Pros

Single Responsibility Principle. Classes can be decoupled: Invocations and executing can be done in seperate classes.

Open/Closed Principle. New commands can be introduced without breaking existing code.

Undo/redo can be implemented. Sometimes, Chain of Responsibility might be better.

Assemble single commands into more complex ones. Approach can be used to create sets of commands to perform complex operations.

Cons

Might overcomplicate things. Usage of the command pattern introduces a whole new layer between senders and receivers.

Relation with other patterns

  • Chain of Responsibility passes a request through a chain of receivers until it’s handled
  • Mediator connects senders and receivers through a mediator object
  • Observer allows receivers to subscribe and unsubscribe dynamically
  • Commands can be used as handlers in Chain of Responsibility or as the request object itself
  • Command and Memento can be used together for versioning and rollback capabilities
  • Command and Strategy are similar but have different intents. The Command pattern encapsulates a specific operation as an object, while the Strategy pattern encapsulates multiple related algorithms as objects. They both encapsulate behavior into objects, but their intent and implementation differ.
  • Prototype can help with saving copies of Commands.
  • Visitor can execute operations over various objects of different classes. It’s a more powerful version of the Command Pattern.

About this series

I noticed personally, that design patterns still remain a mistery to a lot of software engineers, even though these concepts are nothing new and have been around for decades.

I want to solve this problem with this article series to help other software engineers grow.

Feel free to comment in case you find mistakes or want to add your own perspective!

(Photo by the author, Illustrations by Takashi Mifune under free use)

--

--

Martin Jurran
Software Design Patterns

Personal blog of a Software Engineer | Azure DevOps, C#/.NET, JavaScript