Menu
Coddy logo textTech

Command Pattern

Part of the Object Oriented Programming section of Coddy's GO journey — lesson 91 of 107.

The Command pattern encapsulates a request as an object, allowing you to parameterize operations, queue them, or support undo functionality. While Strategy encapsulates algorithms, Command encapsulates entire actions along with their parameters.

In Go, we define a Command interface with an Execute method, then create concrete commands that hold all information needed to perform an action:

type Command interface {
    Execute() string
}

type Light struct {
    IsOn bool
}

type TurnOnCommand struct {
    light *Light
}

func (c *TurnOnCommand) Execute() string {
    c.light.IsOn = true
    return "Light turned on"
}

type TurnOffCommand struct {
    light *Light
}

func (c *TurnOffCommand) Execute() string {
    c.light.IsOn = false
    return "Light turned off"
}

An invoker stores and executes commands without knowing what they do:

type RemoteControl struct {
    command Command
}

func (r *RemoteControl) SetCommand(c Command) {
    r.command = c
}

func (r *RemoteControl) PressButton() string {
    return r.command.Execute()
}

The invoker is completely decoupled from the receiver (the Light):

light := &Light{}
remote := &RemoteControl{}

remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton())  // Light turned on

remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton())  // Light turned off

Command is ideal for implementing undo/redo systems, task queues, or macro recording where you need to store, delay, or replay operations.

challenge icon

Challenge

Easy

Let's build a text editor with undo functionality using the Command pattern! You'll create commands that encapsulate text operations, allowing the editor to execute actions and reverse them—a classic use case that demonstrates why commands are so powerful.

You'll organize your code across three files:

  • command.go: Define your command interface and concrete text editing commands.

    Create a Command interface with two methods: Execute() string to perform the action and Undo() string to reverse it.

    Implement two command types that operate on a *TextEditor:

    • InsertCommand — holds the editor pointer and text to insert. Execute appends the text to the editor's content and returns Inserted: [text]. Undo removes that text from the end and returns Undone insert: [text]
    • DeleteCommand — holds the editor pointer and a count of characters to delete from the end. Execute removes those characters (storing them for undo) and returns Deleted: [removed text]. Undo restores them and returns Undone delete: [restored text]
  • editor.go: Create your text editor (the receiver) and the invoker that manages command history.

    Build a TextEditor struct with a Content field (string) and methods to Append(text string) and DeleteLast(count int) string (returns the deleted text).

    Build an EditorInvoker struct that holds a slice of executed commands as history. Add these methods:

    • ExecuteCommand(cmd Command) string — executes the command, adds it to history, and returns the result
    • UndoLast() string — removes the last command from history, calls its Undo, and returns the result. If history is empty, return Nothing to undo
  • main.go: Demonstrate your command system with a series of operations.

    Read a count of operations. For each operation, read the operation type (insert, delete, or undo). For insert, also read the text to insert. For delete, read the character count. Execute each operation through your invoker and print the result. After all operations, print the final editor content as Final: [content].

The following inputs will be provided:

  • Line 1: Number of operations
  • For each operation: operation type, then additional data if needed (text for insert, count for delete)

For example, given:

5
insert
Hello
insert
 World
delete
3
undo
undo

Your output should be:

Inserted: Hello
Inserted:  World
Deleted: rld
Undone delete: rld
Undone insert:  World
Final: Hello

And given:

4
insert
Go
insert
Lang
undo
insert
!

Your output should be:

Inserted: Go
Inserted: Lang
Undone insert: Lang
Inserted: !
Final: Go!

And given:

2
undo
insert
Test

Your output should be:

Nothing to undo
Inserted: Test
Final: Test

Notice how each command encapsulates everything needed to both perform and reverse its action. The invoker doesn't know what the commands do—it just executes them and maintains history for undo support!

Cheat sheet

The Command pattern encapsulates a request as an object, allowing you to parameterize operations, queue them, or support undo functionality.

Define a Command interface with an Execute method:

type Command interface {
    Execute() string
}

Create concrete commands that hold all information needed to perform an action:

type Light struct {
    IsOn bool
}

type TurnOnCommand struct {
    light *Light
}

func (c *TurnOnCommand) Execute() string {
    c.light.IsOn = true
    return "Light turned on"
}

type TurnOffCommand struct {
    light *Light
}

func (c *TurnOffCommand) Execute() string {
    c.light.IsOn = false
    return "Light turned off"
}

An invoker stores and executes commands without knowing what they do:

type RemoteControl struct {
    command Command
}

func (r *RemoteControl) SetCommand(c Command) {
    r.command = c
}

func (r *RemoteControl) PressButton() string {
    return r.command.Execute()
}

Usage example showing decoupling between invoker and receiver:

light := &Light{}
remote := &RemoteControl{}

remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton())  // Light turned on

remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton())  // Light turned off

For undo functionality, add an Undo method to the Command interface and maintain command history in the invoker.

Try it yourself

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Read number of operations
	line, _ := reader.ReadString('\n')
	numOps, _ := strconv.Atoi(strings.TrimSpace(line))
	
	// Create the editor and invoker
	editor := &TextEditor{}
	invoker := &EditorInvoker{}
	
	for i := 0; i < numOps; i++ {
		// Read operation type
		opLine, _ := reader.ReadString('\n')
		opType := strings.TrimSpace(opLine)
		
		var result string
		
		switch opType {
		case "insert":
			// Read text to insert
			textLine, _ := reader.ReadString('\n')
			text := strings.TrimSuffix(textLine, "\n")
			
			// TODO: Create an InsertCommand and execute it through the invoker
			_ = text
			_ = editor
			
		case "delete":
			// Read count of characters to delete
			countLine, _ := reader.ReadString('\n')
			count, _ := strconv.Atoi(strings.TrimSpace(countLine))
			
			// TODO: Create a DeleteCommand and execute it through the invoker
			_ = count
			
		case "undo":
			// TODO: Call UndoLast on the invoker
			
		}
		
		fmt.Println(result)
	}
	
	// Print final content
	fmt.Printf("Final: %s\n", editor.Content)
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming