Command - kgleong/software-engineering GitHub Wiki

Motivation

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.

Code

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.

Use Case

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.

References

⚠️ **GitHub.com Fallback** ⚠️