by Zoran Horvat
While reading the Refactoring to Patterns book by Joshua Kerievsky, I have encountered a passage which has immediately grabbed my attention. It regards the Command pattern, and here is what it says:
“A question that often arises in extreme programming (XP) circles is what to do when you aren’t sure whether a system will need undo/redo. Do you just implement the Command pattern in case the need arises? Or is that a violation of “You aren’t gonna need it,” an XP principle that cautions against adding functionality to code based on speculation, not genuine need.” – Joshua Kerievsky, Refactoring to Patterns
What immediately made me think was the indicated connection between the Command and undo/redo. What do they have to do with each other?
Author continues his line of thought and goes even further in the same direction:
“If I’m not sure whether a system needs the Command pattern, I generally don’t implement it, for I find that it isn’t that hard to refactor to this pattern when the need arises. However, if your code is getting into a state in which it will be harder and harder to refactor to the Command pattern and there’s a good chance you’ll soon need an undo/redo capability, it may make sense to refactor it to use Command before doing so will be impossibly hard. It’s a bit like taking out an insurance plan.” – ibid.
And again, the question comes to mind – what does undo/redo have to do with the command? Those are related concepts, I admit, but still distinct concepts – not two, but three distinct concepts – and they should not be mixed together when making decisions. Undo/redo functionality is best applied when commands are already added as an explicit design element. In that respect, we can say that command is a prerequisite for undo/redo. But the opposite doesn’t hold. If the system doesn’t need undo/redo, that doesn’t mean that we should keep away from implementing the Command pattern.
And even with Command pattern implemented, you will quickly realize that adding undo and redo functionalities (yes, functionalities, two of them!) can easily be left for later development. As code evolves, as the need for them arises, these two functionalities can be added to the affected commands. Command pattern is not an insurance plan, as the author suggests, but a regular evolutionary curve of the design like any other.
One of the most typical situations in object-oriented design occurs when one object, Context in the diagram below, manipulates a large body of objects, named State in the diagram.
State doesn’t have to be a true collection of data. It can be anything that is being manipulated – a file, a document content, an object model of a monetary transaction, anything. The name State simply indicates that this object graph will be different after an operation has been performed on it.
Problems start piling up when number of possibilities begins to grow. If context can change the state in many ways, then its own implementation will become complex. The surface of the Context object will begin to grow beyond reasonable boundaries, as the next diagram indicates.
Try to imagine a graphical application as an example, where state is the image, or a diagram which is being edited. Context would hold the diagram, and expose numerous operations, such as adding a shape, scaling, moving or rotating a shape, substituting one shape for another, changing line and fill color and patterns, let alone operations such as cut/copy and paste, or saving and loading the diagram. That is way too many responsibilities for a single class.
That is where Command pattern comes to the table. As it usually goes with design patterns, its duty is to turn responsibilities on the side, so that instead of having all of them located in a single class, each responsibility becomes one separate class. If we wanted to paint the surface of a shape in that graphical editor, then there would be a class which only knows how to paint the surface. If we wanted to scale the shape, then there must be a class which only knows how to scale the dots on the drawing surface.
The next diagram shows usual implementation of the Command pattern. Take a look at it, and then we will briefly discuss it.
Command is an abstract entity. Context is organizing commands, and that remains its only responsibility in respect to manipulating the state graph. When the time comes to perform an operation, no matter how complex an operation, all that remains for the Context object is to call the Execute method on a concrete command object.
On the other hand, there is an open list of concrete command classes, all of them deriving from the abstract command, or implementing the command interface. Each concrete command is responsible for one concrete manipulation of the state graph. Concrete commands can depend on the whole state graph, or only on parts of it. That is the implementation detail.
The main takeaway from introducing the Command pattern to the design is that the Context object, which was bound to grow enormously, will now be next to trivial. You don’t have to introduce the Command pattern if context is small enough to survive. You don’t have to introduce the pattern if context was designed as a proof of concept, and it is not certain that it will be maintained and developed ever again.
On the other hand, if development does continue, you will likely want to split responsibilities into smaller classes, and Command pattern would then be one of the natural candidates for the inevitable refactoring.
Now that we understand the core purpose of the Command pattern, we can look where undo and redo functionalities fit into it.
Command is meant to change the system’s state, which naturally brings the question up regarding the inverse change. Resizing a shape in a graphical editor can easily be reversed, by setting the previous size on the same shape. That operation is known as Undo. Once Command pattern is installed in a design, and every command is equipped with the Execute method, it becomes a natural home for Undo, too.
By looking at this diagram, you may come to believe that Undo is really another operation on the Command object. But that is only a convenience. In reality, Undo is yet another command. It is very easy to see that: What is Undo doing? – It is manipulating the system’s state. That is precisely what any command does.
If you still don’t see it that way, then compare the following two segments of code:
resize.Execute();
resize.Undo(); // implicit command
resizeForward.Execute();
resizeBackward.Execute(); // explicit command
Former case shows one object with two antipodal operations (which have the same signature, by the way). Latter case displays two antipodal objects with the same operation on them. The two cases are identical. They can be morphed into one another without disturbing the Context object. Undo operation is usually added to the Command interface for convenience, and that is all.
But that is not to say that the undo functionality must be a proper function. There are other possibilities at your disposal, especially if undoability is optional. Your design might allow undo of some operations, but not of others. It makes no sense to undo a save file operation, for example, even though a change in the document, before or after a save, is a perfectly valid target for undoing.
Here is one possible design which supports optional Undo:
This design acknowledges that only some of the commands support undoing. Resize and Move commands are undoable, while Load and Save are not. Undoable or not, each command supports the Execute method. Therefore, the caller can execute any of them, but only document-editing commands (Resize and Move) can be undone on request.
There are other possible designs to the same end. The next step in developing a design may be to add the redo functionality, and that will also emphasize the command-like behavior of all three (do, undo and redo) operations.
Suppose that there is a requirement to let the user redo the last operation. The simplest possibility is that the command is applicable multiple times:
command.Execute(); // do
...
command.Execute(); // redo
Do not mix this scenario with idempotence. Idempotent commands can be executed multiple times, but they only produce the change once. Redoing a command means to repeat the change it makes. Aim for simple re-executable commands whenever possible if you wish to support redo functionality. That, however, is not always possible.
In a more complex scenario, a redo operation for a given command would be different than the Execute function. Then, you can support that function, too. Besides Execute and Undo, we would have a Redo function.
That situation can be turned upside-down if we recognize, once again, that Undo and Redo are just additional commands. A command can then expose methods for fetching true undo and redo commands, rather than executing their behaviors. Below is the code which demonstrates that idea.
interface ICommand
{
void Execute();
ICommand GetRedo();
ICommand GetUndo();
}
class Save : ICommand
{
public void Execute() { ... }
public ICommand GetRedo() =>
this; // save again to redo
public ICommand GetUndo() =>
new DoNothing();
// cannot undo saving to disk
}
class MoveShape : ICommand
{
private Vector Amount { get; }
public void Execute() { ... }
public ICommand GetRedo() =>
this; // move again to redo
public ICommand GetUndo() =>
new MoveShape(-this.Amount);
// move in the opposite direction
}
class DeleteShape : ICommand
{
private Shape Target { get; }
public void Execute() { ... }
public ICommand GetRedo() =>
new DoNothing();
// cannot delete the same shape again
public ICommand GetUndo() =>
new AddShape(this.Target);
// add the deleted shape back
}
This sample code is far from over. Managing undo and redo requires tracking command history, which is usually organized as a stack. Undoing a command removes that command from history, while redoing would push the redo version of the last command to the history. In case that DoNothing command is used to indicate unsupported undo/redo operations, you would have to pay attention to skip them.
There are more complications which I will not mention here. Developing a fully functional undo/redo subsystem is outside the scope of this analysis, and I will stop here. Let me just tell you that there is no universal undo/redo solution, but the one you develop should always come out of the needs and existing system design.
In this article, I have attempted to show that undo and redo operations are two additional kinds of commands, and that there is no substantial difference between the common Execute operation on a command and its mirror images Undo and Redo.
It is often convenient to add one or both operations to the base command type. Keep in mind, though, that this is only a convenience. And only do that if these operations will make sense on all concrete commands. If this is not convenient in current situation, then you can still opt for representing both undo and redo as separate commands.
If you reflect at where this article has started, it was about the claim that we should know whether undo and redo operations are a requirement when deciding whether to apply the Command pattern or not. In this article, my intention was to demonstrate that the opposite is true, to demonstrate that these concepts are following their separate ways.
You can apply the Command pattern regardless of the system’s configuration, or current and future requirements. Command is an effective way of separating the execution context from numerous isolated operations, and that is all. Undoing or redoing commands are orthogonal axes, which can be added to the existing commands system when needed. It is equally possible to only implement one Execute function in commands and never to add undo/redo to them.
If you wish to learn more, please watch my latest video courses
In this course, you will learn the basic principles of object-oriented programming, and then learn how to apply those principles to construct an operational and correct code using the C# programming language and .NET.
As the course progresses, you will learn such programming concepts as objects, method resolution, polymorphism, object composition, class inheritance, object substitution, etc., but also the basic principles of object-oriented design and even project management, such as abstraction, dependency injection, open-closed principle, tell don't ask principle, the principles of agile software development and many more.
More...
In this course, you will learn how design patterns can be applied to make code better: flexible, short, readable.
You will learn how to decide when and which pattern to apply by formally analyzing the need to flex around specific axis.
More...
This course begins with examination of a realistic application, which is poorly factored and doesn't incorporate design patterns. It is nearly impossible to maintain and develop this application further, due to its poor structure and design.
As demonstration after demonstration will unfold, we will refactor this entire application, fitting many design patterns into place almost without effort. By the end of the course, you will know how code refactoring and design patterns can operate together, and help each other create great design.
More...
In four and a half hours of this course, you will learn how to control design of classes, design of complex algorithms, and how to recognize and implement data structures.
After completing this course, you will know how to develop a large and complex domain model, which you will be able to maintain and extend further. And, not to forget, the model you develop in this way will be correct and free of bugs.
More...
Zoran Horvat is the Principal Consultant at Coding Helmet, speaker and author of 100+ articles, and independent trainer on .NET technology stack. He can often be found speaking at conferences and user groups, promoting object-oriented and functional development style and clean coding practices and techniques that improve longevity of complex business applications.