Command - kgleong/software-engineering GitHub Wiki
The Command design pattern encapsulates a request and any associated actions into an object.
This pattern has the following consequences:
- Calling code only needs to issue a request. Knowledge of the objects handling the request is unnecessary.
- Swapping request handlers can be done dynamically.
- This pattern is the object oriented equivalent to callbacks.
- Undo operations can be supported.
public interface Command {
void execute();
}
public class ConcreteCommand implements Command {
Receiver mReceiver;
public ConcreteCommand(Receiver receiver) {
mReceiver = receiver;
}
public void execute() {
System.out.println("ConcreteCommand execute method called.");
mReceiver.performOperation();
}
}
At a minimum, a Command
object implements an execute()
method.
If necessary, request parameters can be passed into the execute()
method or set on the Command
instance.
To support undoable actions, the Command
object can track state and supply an undo()
method that reverts any changes that occurred as a result of the execute()
method.
Composite Command
objects can be modeled by adding a list of Command
objects:
public class ConcreteCompositeCommand implements Command {
List<Command> mCommandList = new LinkedList<>();
public void execute() {
for(Command command : mCommandList) {
command.execute();
}
}
}
This is follows the Composite design pattern.
public interface Receiver {
void performOperation();
}
public class ConcreteReceiver {
pubic void performOperation() {
System.out.println("ConcreteReceiver performing an operation.");
}
}
When a request is triggered, the Receiver
instance associated with this Command
object handles the request.
The Command
object can optionally decide to supply the full implementation of the execute()
method, therefore eliminating the need for an associated Receiver
instance.
Calling code only needs knowledge of the execute()
method, which adds a good degree of flexibility with regard to how requests are handled.
public class Invoker {
Map<String, Command> mCommandMap = new HashMap<>();
public void registerCommand(String key, Command command) {
mCommandMap.put(key, command);
}
public void executeCommand(String key) {
Command command = mCommandMap.get(key);
command.execute();
}
}
Rather than having clients instantiate and wire Command
and Receiver
instances, an Invoker
object can perform the configuration.
The Invoker
retains references to any relevant Command
instances and is responsible for serving the correct Command
object based on supplied request parameters.
// Client usage
Command command = new ConcreteCommand(new ConcreteReceiver());
Invoker invoker = new Invoker();
String key = "A"
invoker.registerCommand(key, command);
invoker.executeCommand(key);
// Output
// ConcreteCommand execute method called.
// ConcreteReceiver performing an operation.
Applications frequently contain menus, which allow users to peform mutliple commands like:
- Opening a file.
- Pasting copied text into a document.
public class Menu extends View {
List<View> mMenuItemList = new ArrayList<>();
}
public class MenuItem extends View {
String mTitle;
Command mCommand;
public void onClick() {
mCommand.execute();
}
}
A Menu
contains list of MenuItem
instances. For example, clicking on one MenuItem
pastes the contents of the clipboard into the current document. Clicking on another may allow the user to open a document.
In turn, each MenuItem
must handle a user click. When a user clicks on an item, the onClick()
method is triggered, and the MenuItem
delegates the request to an associated Command
object.
public class PasteCommand implements Command {
Application mApplication;
public PasteCommand(Application application) {
mApplication = application;
}
public void execute() {
Document currentDocument = mApplication.getCurrentDocument();
String clipboardContents = mApplication.getClipboardContents();
Point cursorPosition = mApplication.getCursorPosition();
// Insert clipboard text into the current document
currentDocument.insertTextAtPosition(clipboardContents, cursorPosition);
}
}
In this scenario, the PasteCommand
class is able to ascertain all necessary information from the application. It supplies the complete implementation for its own execute()
method.
The internal details of pasting text is now hidden from the MenuItem
that contains it. Since MenuItem
instances are graphical UI elements, it makes sense that they should be decoupled from the actual logic needed to execute user actions.