037
behavioral GOF

Command

Reference Wikipedia ↗
Command — class diagram
Plate 037 class diagram

The Command pattern encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. Essentially, it decouples the object that originates the request from the object that knows how to perform it.

This pattern promotes loose coupling and makes the system more flexible. It’s particularly useful when you need to manage a history of actions for undo/redo functionality, delay or queue the execution of a request, or when you want to combine basic operations into more complex ones.

Usage

The Command pattern is widely used in scenarios requiring transaction management, logging, and undo/redo capabilities. It’s core to many editor implementations, allowing for actions like copy, paste, and delete to be tracked and reversed. Modern frameworks often leverage the command pattern, either explicitly or implicitly, for event handling and asynchronous task processing. It’s also common in game development for handling player actions.

Examples

  1. GUI Frameworks (e.g., Qt, Swing): Most GUI frameworks utilize the Command pattern for handling user interactions. When a user clicks a button, the framework creates a command object representing that action (e.g., FileOpenCommand, ButtonClickHandler). This command is then executed by a receiver (e.g., a file system or a widget). This allows for easy undo/redo functionality and event logging.

  2. Database Transactions: Database systems internally use a command pattern to manage transactions. Each SQL statement is treated as a command. These commands are bundled into a transaction, and the database engine can then commit or rollback the entire set of commands, ensuring data consistency. An example might be UpdateAccountCommand, DeleteRecordCommand, associated with a TransactionManager.

  3. Undo/Redo Systems in Text Editors: Text editors like Notepad++ or VS Code use the command pattern so that actions like typing, deleting, or formatting text are all commands. These commands can be stored in a stack for undo/redo functionality. Each command holds enough information (e.g., the deleted text, the old formatting) to reverse its effect.

Specimens

15 implementations
Specimen 037.01 Dart View specimen ↗

The Command Pattern encapsulates a request as an object, allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations. This implementation uses Dart’s function type and abstract class capabilities to define a command interface with an execute method. Concrete commands wrap specific operations on a SimpleEditor receiver. An EditorInvoker manages command execution and potentially queuing. Dart’s concise syntax and support for first-class functions make this pattern particularly clean to implement, avoiding excessive interface bloat often seen in other languages. The use of an abstract class provides type safety and a clear contract for all commands.

abstract class Command {
  void execute();
  void unexecute(); // Optional for undo functionality
}

class SimpleEditor {
  String text = '';

  void setText(String newText) {
    text = newText;
    print('Text set to: $text');
  }

  void appendText(String addedText) {
    text += addedText;
    print('Text appended: $text');
  }
}

class SetTextCommand implements Command {
  final SimpleEditor editor;
  final String text;
  String? previousText;

  SetTextCommand(this.editor, this.text);

  @override
  void execute() {
    previousText = editor.text;
    editor.setText(text);
  }

  @override
  void unexecute() {
    editor.setText(previousText ?? ''); // Restore previous state
  }
}

class AppendTextCommand implements Command {
  final SimpleEditor editor;
  final String text;

  AppendTextCommand(this.editor, this.text);

  @override
  void execute() {
    editor.appendText(text);
  }

  @override
  void unexecute() {
    // Simple un-append.  More complex scenarios might need storing more state.
    if (editor.text.endsWith(text)) {
      editor.text = editor.text.substring(0, editor.text.length - text.length);
       print('Text un-appended: ${editor.text}');
    }
  }
}


class EditorInvoker {
  final List<Command> commands = [];

  void executeCommand(Command command) {
    command.execute();
    commands.add(command);
  }

  void undoLastCommand() {
    if (commands.isNotEmpty) {
      final command = commands.removeLast();
      command.unexecute();
    } else {
      print('No commands to undo.');
    }
  }
}

void main() {
  final editor = SimpleEditor();
  final invoker = EditorInvoker();

  final setTextCommand = SetTextCommand(editor, 'Hello');
  invoker.executeCommand(setTextCommand);

  final appendCommand = AppendTextCommand(editor, ' World!');
  invoker.executeCommand(appendCommand);

  invoker.undoLastCommand();
  invoker.undoLastCommand();

}