command
Define a class that represents an operation.
The command design pattern.
PNG
SVG
The command Design Pattern
Frequency
Complexity

In Object-Oriented-Programming, we have objects representing all kinds of concepts, like database entities, graphical elements, or concrete things modeled after the real world around us. And we have methods we can call on these objects to mimic their behavior and alter their state. Both concepts, objects and methods, are first-class citizens in most OOP languages - essential aspects made explicit by the corresponding language.

Calling a method, however, is not part of this group.

The process of calling a method (often also considered a request) is not something we can grab and manipulate, at least not in the most common object-oriented languages: We cannot take a specific method call and store it in a list, nor send it over the wire to execute it elsewhere.

But having such a feature would be very convenient. If we would be able to store the history of all requests a user made, we could provide functionality like undo/redo or event let users record several actions to form a macro. Also, tracking bugs would be much easier - just record all steps the user took and iterate through them one by one to recreate the error state. We could even send a user action from one device to another and execute it there.

The Command pattern gives us the possibility to do all this.

Sounds interesting? Well, then, let's go on...

Classes and Methods

One of the primary concepts in OOP are classes. A class is a blueprint that describes the shape and behavior of an object. An object is an instance of a class created during runtime. If we want to know which objects exist in our application, we could store them in a list to keep track of them. This is possible because objects are a central OOP concept, and languages provide many different ways to interact with them. Behavior, on the other side, is represented by methods.

A class with a single method.
In OOP, methods are used to specify behavior.

A method contains the implementation code that describes a specific behavior of an object during runtime. In contrast to classes and objects, methods don't have a runtime representation. There is not such a concept as a method instance (which would basically be a request). They only have a static representation without a runtime equivalent.

While this is enough for most use cases, this approach has some drawbacks. Once executed, we cannot track how many of these requests we made or what parameters they received during the call. We simply cannot do the same with them as we could do with objects.

Luckily, the solution to this problem is both simple and smart: If objects have such comprehensive support in our language, why not also use them to represent the requests fired in our application? Instead of having a method implemented by a class, we extract all this logic into a separate class that behaves like our former method.

And this brings us already to the Command pattern.

The Command Pattern

Instead of a method inside a class, we now have a dedicated Method class. Our new class has everything we implemented in our method before, including information about the caller and our request's arguments. Since we still need a way to invoke our new "Method", our class also needs an execute function. Calling it is the equivalent of calling a method.

A class invoking another class representing a method.
A class representing a method.
Calling a method becomes a relation between two classes.

The class that hosted our method implementation before is not gone. It will still have to interact with our new Method class. It is often called the Receiver, as it's the actual target of our action.

Since our method is now a class, an instance of that class represents a call of this method. And that is exactly what we wanted to achieve: Our method calls have become first-class citizens.

This approach already resembles most of the Command pattern. There is only a slight addition we have to make, which is providing a general command interface so that all types of commands can be handled uniformly. In that way, separate command classes can implement different action logic and be invoked in a standardized way through the caller.

A command interface implemented by two concrete command classes.
The Command pattern uses a common interface for all Command-classes.

Commands have many benefits. Some of them are

  • Decoupling: With commands, we can reduce the coupling between the caller and the action. Think, for instance, of a menu item in an application: The menu item can hold a reference to a command object without knowing what its actual implementation may look like. We can even bind the same command object to different user interface elements.

  • Undo/Redo Support: Commands are often the base for implementing an undo/redo mechanism. Since we can store them in a list, it's relatively easy to navigate this list and (re)execute them as often as we want. We will discuss some implementation details regarding undo/redo mechanism a bit later.

  • Transactions: Another use case is the implementation of transaction handling. Here, several commands are executed successively. But only if all operations succeed the whole transaction is considered successful. As soon as a single action fails, we have to revert all previous ones to return to the initial state.

Implementation Details

Commands are frequent - almost all applications use them in one way or another. That's why there exist many variations of this pattern. Choosing the proper implementation may depend on your use case but also on your preferences regarding some of the following aspects.

Providing Command Context

One central question to answer is how to inject all relevant context information, like receivers and parameters, into your commands. One way would be to have an execute method that accepts a parameter of type Object or any. The concrete command implementations are responsible for casting this parameter to the desired type. This approach is, for instance, used in the Windows Presentation Foundation (WPF).

A command implementation casting its incoming parameter to a concrete receiver.
Commands can receive their context as parameter but have to cast it to the correct type.

Although easy to implement, there is a disadvantage. You lose type safety when casting the parameter to the desired type. This can be problematic when you try to reuse your command in a different context. While your code will compile without errors, your cast may fail during runtime if the new context is incompatible with the old one.

Another option is to equip the command with all relevant information during construction. In this case, we don't have to pass any parameters when calling - as long as all relevant data can be derived from the constructor parameters. This approach may work better for specific scenarios as it lessens the coupling between the command and its context, allowing for easier reuse.

A command receiving all necessary dependencies during construction
A command that receives all its dependencies already through its constructor.

Which of the two strategies you choose will also affect the "weight" of your commands. Let's see what this means.

Heavy or Lightweight Commands

Commands with sophisticated logic that need to orchestrate many receivers are definitely heavy-weight. They have many dependencies, and there is a high chance that a change in any of them will propagate to your command implementation.

UMLBoard's Delete command is an example for such a heavy command: When the user deletes an element - like a class or an interface - the following things happen:

  • All members of the class must be deleted
  • All incoming and outgoing relations must be tracked and deleted.
  • When another element has a member or method parameter referencing the deleted class, these must also be deleted.
  • For each element from which we removed a member, we need to recalculate its layout
  • We also have to remember the ids of all removed elements to later restore them during an undo operation.

Performing all these steps is not trivial and requires many components to interact with each other. Placing all this inside the command's execute method makes its relatively heavy. While this improves the cohesion of our code - everything we need to know about our actions is located in one place - there is also a drawback: Dependencies make it challenging to serialize and transfer our code between processes or over the internet. It may be easy to persist the command and its parameter, but how should we do this with all its dependencies, especially if they manage their own state?

In this case, a more lightweight solution is often a better choice.

Redux, for instance, uses such a lighter command model. Redux's Actions are conceptually similar to Commands, as they represent the operations users can execute to change the application's state. However, since they are simple JSON objects consisting only of an id and a payload - without any implementation logic - they are much lighter. The implementation moves entirely to the receivers, called reducers in Redux. Each reducer has to define which type of action it can handle and how this action would affect the current state.

This lightweight command concept solves many of the problems we had with our previous approach: A Redux Action has no dependencies, hence reusing them for new reducers is possible without changing any existing code. And since serializing JSON objects is also very easy, we can send them between processes or store them in a queue to undo them later.

Expressing our aforementioned Delete command as a Redux Action could, for instance, look like this:

type DeleteCommand = {
    type: 'delete', // the id of our action
    payload: {
        elementIds: string[] // ids of the classes we're going to delete
    }
}

The actual delete logic would be implemented by a DeleteService in the backend. This is also the approach UMLBoard uses. If you want to know more about UMLBoard's command handling and inter-process-communication, please see this article for some implementation details.

Which approach you choose highly depends on your use case, especially whether you require your commands to be serializable or not.

Undo/Redo Handling

If you already use the Command Pattern in your application, adding Undo/Redo logic is relatively straightforward.

A common approach is to have an additional Invoker class responsible for executing each command - and two queues, one for storing the commands that could be undone and another one for redoing them again. Before the Invoker processes an undoable command, it stores it in the undo queue. When the user executes the Undo operation (which, interestingly, can be a command by itself, although not an Undoable one), the Invoker pops the latest entry from the undo stack and calls its undo operation. After that, the command object is put on the redo stack so that it gets executed the next time the user invokes Redo.

An undo command calling an invoker class which pops the latest command from the undo stack.
When undoing a command, the invoker pops the latest command from the stack and calls its undo-method.

For this mechanism to work, you must store the application state before executing the command, so that you can later restore it again. Depending on the command logic, this could be done in different ways:

  • For simple actions, you can derive the old state by directly reverting the logic implemented in the command. E.g., a simple action like adding a number to a value can be undone by subtracting the value again.
  • In more complex scenarios, you may have to remember the original state to restore it later. This could be done with a Memento object you store together with your command. To undo the command, you must apply the Memento to the altered state to restore its original condition.

The latter approach may be better suited for lightweight command strategies, as there is no special command logic you can revert.

While undo/redo support is very convenient, keep in mind that it is often an all-or-nothing decision: If you let users undo their actions, you must implement this behavior for most of your commands. Otherwise, there will be strange "gaps" in your command history when undoing several steps successively.

You will also have to consider how you want to undo delete operations. A common approach here is to use a soft delete strategy, meaning you mark deleted entities with a flag and filter them out during requests. For recreating the deleted items, you have to reset the flag again. While relatively easy to implement initially, adding this mechanism to an existing code base may require some effort as you must update most of your queries.

References