Design Pattern: Command Pattern

Summary

Pattern NameCommand Pattern
Pattern TypeBehavioral Pattern
ScopeObject
Other NamesAction
Transaction
TaglineWrap request in an object
Use cases1. When we want to create an object to store a command/request.
2. When we want to support features like undo or central logging of commands.
Related PatternsComposite
Memento
Difficulty LevelMedium
Implementations

Definition

Command pattern wraps a request/command in an object. Then we can pass the object anywhere and later when we want to execute the command the object can be used.

As the request is wrapped in an object, the execution of the command is completely separate from the request-sending part. Also, the Command pattern can be used to support features like undoing and logging commands.

The state of the command object is wrapped with the command, which makes execution and undoing (and redoing) of the command possible. The logging of commands enables system recovery in case of a system crash.

Command pattern enables adding a new command very easy, as new command can be added without any change in existing code.

The client can issue any of the commands, without any knowledge of the command details or the receiver of the command. The lifetime of a command object can be independent of the sent request.

Use Cases

Here are a few uses of the Command pattern-

  • When we want to store a command/request in an object.
  • When we want to separate request/command sending and execution process.
  • When we want to support the undoing and/or redoing commands.
  • When we want log commands, to support system recovery in case of a crash.
  • When we want to add new commands without any change in existing code.
  • When we want to queue requests/commands and execute those at different times.

Implementation

Command pattern has 3 elements involved in the implementation.

  1. Command Interface: an interface(or abstract class) to ensure the common interface for all command classes.
  2. Concrete Command Classes: classes that implement the command interface and are responsible for executing command.
  3. Command Controller: class to take the command as param and executed the command. This controller can also contain additional functionality.

Take a look at the following diagram to understand the implementation of Command pattern.

Follow the steps below to implement Command pattern:

  1. Create an interface to ensure a common interface for all command classes.
  2. Create command classes and implement command interface.
  3. Create a command controller class. In this class create functions that take the command object as a param and execute functions/commands from that command object. This class can contain additional functions if required by the client.
  4. In the client create objects of command and pass that to the controller class functions for execution.

Examples

Example #1: Simple Command Pattern

Command [Interface and Concrete Command Classes]

// Command interface

interface CommandI

    execute()
    undo()

end interface


// First command class

class Command1 implements CommandI

    method execute()
        output "Command1: Executing"
    end method

    method undo()
        output "Command1: Undoing"
    end method

end class


// Second command class

class Command2 implements CommandI

    var prop1: String

    constructor(prop1Param: string) {
        prop1 = prop1Param
    }

    method execute()
        output "Command2: executing -- Prop1 value: " + prop1
    end method

    method undo()
        output "Command2: undoing"
    end method

end class

Controller

// Command controller

class CommandControl

    var commandList: CommandI[] = []

    method execute(command: CommandI)
        // Call the command execution method
        command.execute()

        // Add command to list to keep track of the command
       commandList.add(command)
    end method

    method remove(command: CommandI)
        // Call the undoing process of the command
        command.undo()

        // Find the command and remove command from list
        cocmmandList.remove(command)
    end method

    // Undo the last executed command
    method undo()
        // Find the last element
        var lastElementIndex = commandList.size() - 1
        var lastCommand = commandList.get(lastElementIndex)

        // Remove command
        commandList.remove(lastCommand)
    end  method

end class

Demo

// Demo
var commandControl = new CommandControl()


var command1 = new Command1()
commandControl.execute(command1)


command2 = new Command2("my first prop")
commandControl.execute(command2)


command3 = new Command1()
commandControl.execute(command3)


command4 = new Command2("your command 2 prop")
commandControl.execute(command4)


// Undo specific command
commandControl.remove(command1)


// Undo last command
commandControl.undo()

Output

Command1: Executing
Command2: executing -- Prop1 value: my first prop
Command1: Executing
Command2: executing -- Prop1 value: your command 2 prop

Command1: Undoing
Command2: undoing

Example #2: UI Elements

Let’s take the example of printing UI elements.

Command [Interface and Concrete Classes]

// Command interface

interface UiCommand 

    print()

    remove()

end interface


// Class for handlign Input element

class InputUi implements UiCommand

    method print
        // Write code to print the element
        output "Printing Input"
    end method

    method remove
        // Write code for removing the element
        output "Removing Input"
    end method

end class


// Class for handling Button element

class ButtonUi implements UiCommand

    var final name: String

    constructor(nameParam: String) 
       name = nameParam
    end constructor

    method print
        // Write code to print the element
        output "Printing " + name + " Button"
    end method

    method remove()
        // Write code for removing the element
        output "Removing " + name + "  Button"
    end method

end class


// Class for handling Table element

class TableUi implements UiCommand
    
    method print
        // Write code to print the element
        output "Printing Table"
    end method

    method remove
        // Write code for removing the element
        output "Removing Table"
    end method

end class

Controller

class UiControl

    // Array to hold the list of executed commands
    var private commandList[]

    method addElement(UiCommand command) {
        // Execute command
        command.print()

        // Store command in list to have a history
        commandList.add(command)
    end method

    public void removeElement(UiCommand command) {
        // Remove element
        command.remove();

        // Store command in list to have a history
        commandList.remove(command)
    end method

    public void undo() {
        // Get the last element
        var lastElementIndex = commandList.size() - 1
        var lastCommand = commandList.get(lastElementIndex)
        
        // Remove last element
        removeElement(lastCommand)
    end method

end class

Demo

var uiControl = new UiControl()

var inputUi = new InputUi()
var tableUi = new TableUi()
var buttonUi = new ButtonUi("Submit")

uiControl.addElement(inputUi)
uiControl.addElement(tableUi)
uiControl.addElement(buttonUi)

// Remove specific element
uiControl.removeElement(tableUi)

// Add some new elements
uiControl.addElement(new ButtonUi("Cancel"))
uiControl.addElement(new TableUi())
uiControl.addElement(new InputUi())
uiControl.addElement(new ButtonUi("Wrong button"))

// Undo last to command
uiControl.undo()
uiControl.undo()

Output

Printing Input
Printing Table
Printing Submit Button

------------------------------------

Removing Table

------------------------------------

Printing Cancel Button
Printing Table
Printing Input

------------------------------------

Printing Wrong button Button
Removing Wrong button  Button
Removing Input

Code Implementations

Use the following links to check Command pattern implementation in specific programming languages.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.