Command Design Pattern

overview

DEFINITION

  • IS A BEHAVIORAL DESIGN PATTERN

  • turns a request into a stand-alone object that contains all information about the request

  • parameterize methods with different requests, delay or queue a request’s execution

  • support undoable operations

PLAIN WORDS

Allows you to encapsulate actions in objects. The key idea behind this pattern is to provide the means to decouple client from receiver.

REAL WORLD EXAMPLE

ordering food at a restaurant:
You (i.e.
Client) ask the waiter (i.e. Invoker) to bring some food (i.e. Command) and waiter simply forwards the request to Chef (i.e. Receiver) who has the knowledge of what and how to cook.

The Problem

  • A text editor app with a bunch of buttons
  • Can be for various different operations
  • The buttons may inherit the Button parent class
  • Some buttons may share the same code & business logic

The Problem

Business Logic & User Interface Layer is tightly coupled together

The Problem

Some operations may have different parameters

The Problem

Some operations can be invoked from different places

The SOLUTION

Structure

Structure explaination

CLass Diagram

Pseudocode

// The base command class defines the common interface for all
// concrete commands.
abstract class Command is
    protected field app: Application
    protected field editor: Editor
    protected field backup: text

    constructor Command(app: Application, editor: Editor) is
        this.app = app
        this.editor = editor

    // Make a backup of the editor's state.
    method saveBackup() is
        backup = editor.text

    // Restore the editor's state.
    method undo() is
        editor.text = backup

    // The execution method is declared abstract to force all
    // concrete commands to provide their own implementations.
    // The method must return true or false depending on whether
    // the command changes the editor's state.
    abstract method execute()


// The concrete commands go here.
class CopyCommand extends Command is
    // The copy command isn't saved to the history since it
    // doesn't change the editor's state.
    method execute() is
        app.clipboard = editor.getSelection()
        return false

class CutCommand extends Command is
    // The cut command does change the editor's state, therefore
    // it must be saved to the history. And it'll be saved as
    // long as the method returns true.
    method execute() is
        saveBackup()
        app.clipboard = editor.getSelection()
        editor.deleteSelection()
        return true

class PasteCommand extends Command is
    method execute() is
        saveBackup()
        editor.replaceSelection(app.clipboard)
        return true

// The undo operation is also a command.
class UndoCommand extends Command is
    method execute() is
        app.undo()
        return false


// The global command history is just a stack.
class CommandHistory is
    private field history: array of Command

    // Last in...
    method push(c: Command) is
        // Push the command to the end of the history array.

    // ...first out
    method pop():Command is
        // Get the most recent command from the history.


// The editor class has actual text editing operations. It plays
// the role of a receiver: all commands end up delegating
// execution to the editor's methods.
class Editor is
    field text: string

    method getSelection() is
        // Return selected text.

    method deleteSelection() is
        // Delete selected text.

    method replaceSelection(text) is
        // Insert the clipboard's contents at the current
        // position.


// The application class sets up object relations. It acts as a
// sender: when something needs to be done, it creates a command
// object and executes it.
class Application is
    field clipboard: string
    field editors: array of Editors
    field activeEditor: Editor
    field history: CommandHistory

    // The code which assigns commands to UI objects may look
    // like this.
    method createUI() is
        // ...
        copy = function() { executeCommand(
            new CopyCommand(this, activeEditor)) }
        copyButton.setCommand(copy)
        shortcuts.onKeyPress("Ctrl+C", copy)

        cut = function() { executeCommand(
            new CutCommand(this, activeEditor)) }
        cutButton.setCommand(cut)
        shortcuts.onKeyPress("Ctrl+X", cut)

        paste = function() { executeCommand(
            new PasteCommand(this, activeEditor)) }
        pasteButton.setCommand(paste)
        shortcuts.onKeyPress("Ctrl+V", paste)

        undo = function() { executeCommand(
            new UndoCommand(this, activeEditor)) }
        undoButton.setCommand(undo)
        shortcuts.onKeyPress("Ctrl+Z", undo)

    // Execute a command and check whether it has to be added to
    // the history.
    method executeCommand(command) is
        if (command.execute)
            history.push(command)

    // Take the most recent command from the history and run its
    // undo method. Note that we don't know the class of that
    // command. But we don't have to, since the command knows
    // how to undo its own action.
    method undo() is
        command = history.pop()
        if (command != null)
            command.undo()

Application

WHen to use the command pattern

When you want to parametrize objects with operations.

lets say We want to program a remote control api

but we have so many diff vendor classes with diff operations

with command pattern, we encapsulate each operation into a command

Application

WHen to use the command pattern

You want to queue operations, schedule their execution or execute them remotely.

job queue

Receiver

Invoker

Commands

job queue: The Nodes

// READER
export const ftpReader = new EtlNode(
  1,
  'Extract From FTP',
  new FtpReader({
    host: 'localhost',
    port: 22,
    username: 'wmhafiz',
    password: 'mysuperpassword',
    sourcePath: '/home/source_path',
    destPath: '/home/dest_path',
  })
);
const ftpReaderOutput = ftpReader.run();

// UPPER CASE
export const upperCaseNode = new EtlNode(
  2,
  'Uppercase Company Name',
  new UpperCaseTransformer({
    destField: 'companyName_upper',
    sourceField: 'companyName',
  }),
  ftpReaderOutput
);
const upperCaseOutput = upperCaseNode.run();

// HARMONIZE
export const harmonizeNode = new EtlNode(
  3,
  'Harmonize Company Name',
  new HarmonizeTransformer({
    destField: 'companyName_std',
    dict,
    sourceField: 'companyName_upper',
  }),
  upperCaseOutput
);
const finalOutput = harmonizeNode.run();

const dict = {
  entries: [
    {
      key1: 'Sendirian',
      key2: 'SDN',
      method: TextReplacerMethod.WORD,
    },
    {
      key1: 'SND',
      key2: 'SDN',
      method: TextReplacerMethod.WORD,
    },
  ],
};

JOB QUEUE: The Client

import { harmonizeNode, upperCaseNode, ftpReader } from './node.test';
import { EtlController } from './controller';

export const controller = new EtlController(1, 'Simple ETL Job');
controller.addNode(ftpReader);
controller.addNode(upperCaseNode);
controller.addNode(harmonizeNode);

controller.addRelationship({
  id: 1,
  title: 'Ftp Output',
  sourceNode: ftpReader,
  destNode: upperCaseNode,
});

controller.addRelationship({
  id: 2,
  title: 'Uppercase Output',
  sourceNode: upperCaseNode,
  destNode: harmonizeNode,
});

// operations
controller.start();
controller.pause();
controller.resume();

Application

WHen to use the command pattern

You want to implement reversible operations.

IMPLEMENTING UNDO

IMPLEMENTING UNDO

logging / auto failover

  • Store a backup on every command execution
  • Load the backup upon any server crashing to restore the state

references

Kahoot

Command Design Pattern

By Wan Mohd Hafiz

Command Design Pattern

  • 353