In this tutorial, we’ll learn about the command pattern which is an important behavioral design pattern. It has some important applications like implementing undo/redo functionality in text editors.
In the command design pattern, there’s a command object that sits between the sender and the receiver objects. The sender object can create a command object. The command object then calls the exposed method in the receiver. And so, the sender object doesn’t need to know about the receiver and its exposed methods.
We also have another object known as invoker. An invoker is an object responsible for invoking the appropriate command object to complete a task. We can also use a command manager which keeps track of commands, invokes and manipulates them.
There are some popular use-cases of the command pattern:
As each command object supports do/undo operation, we can extend this functionality to design do/undo operation for a text editor. The idea is to have two lists of command objects – a history and a redo list:
Sounds simple right!
We can represent the command design pattern as:
Where we have,
Moreover, each command class usually provides the implementation of these methods:
Suppose we have to implement the cut-copy-paste functionality for a text editor.
So, we start by defining our Command interface:
public interface Command { void execute(); void unexecute(); default boolean isReversible() { return true; } }
Also, let’s assume we have a Document class supporting text insertion and deletion:
//class which will be our Receiver public class Document { public void insert(String str, int position) { ... } public String delete(int position, int noOfChars) { ... } public void copy(int position, int noOfChars) { ... } }
Now, we’ll define our CutCommand class:
public class CutCommand implements Command { private Document doc; private String text; private int startPosition; private int noOfChars; //suitable constructor public void execute() { this.text = this.doc.delete(startPosition, noOfChars); } public void unexecute() { this.doc.insert(text, startPosition); } }
Let’s also define the other two command classes as well:
public class CopyCommand implements Command { private Document doc; private int startPosition; private int length; //suitable constructor public void execute() { this.doc.copy(startPosition, length); } public void unexecute() { System.out.println("Uncopy operation is blocked"); } public boolean isReversible() { return false; } } public class PasteCommand implements Command { private Document doc; private String text; private int startPosition; //suitable constructor public void execute() { this.doc.insert(text, startPosition); } public void unexecute() { this.doc.delete(startPosition, text.length()); } }
As we know, uncopy is not a valid operation, we have returned false in our isReversible() method of the CopyCommand class.
Finally, we can write an invoker class:
public class DocumentInvoker { private Document document; private CommandManager commandManager; public DocumentInvoker(Document document) { this.document = document; commandManager = CommandManage.getInstance(); } public void cut(int position, int length) { Command cutCommand = new CutCommand(document, position, length); commandManager.invoke(cutCommand); } public void copy(int position, int length) { Command copyCommand = new CopyCommand(document, position, length); commandManager.invoke(copyCommand); } public void paste(String text, int position) { Command pasteCommand = new PasteCommand(document, text, position); commandManager.invoke(pasteCommand); } }
Here, the CommandManager is the class that manages the history and the redo lists. The invoker instantiates the command object with the information it needs and then calls the command manager to finally perform the operation.
In this tutorial, we learned how to implement the Command Design Pattern in Java.
It promotes loose coupling as the sender need not know anything about the receiver and can simply invoke operations.