Menu
Coddy logo textTech

Command Pattern

Part of the Object Oriented Programming section of Coddy's C++ journey — lesson 96 of 104.

The Command pattern encapsulates a request as an object, allowing you to parameterize clients with different requests, queue operations, or support undo functionality. It decouples the object that invokes an operation from the one that knows how to perform it.

The pattern has four key components: a Command interface with an execute() method, Concrete Commands that implement specific actions, a Receiver that performs the actual work, and an Invoker that triggers commands:

#include <iostream>
#include <memory>
#include <vector>

// Receiver - knows how to perform operations
class Light {
public:
    void turnOn() { std::cout << "Light is ON\n"; }
    void turnOff() { std::cout << "Light is OFF\n"; }
};

// Command interface
class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};

// Concrete Commands
class LightOnCommand : public Command {
    Light& light;
public:
    LightOnCommand(Light& l) : light(l) {}
    void execute() override { light.turnOn(); }
    void undo() override { light.turnOff(); }
};

class LightOffCommand : public Command {
    Light& light;
public:
    LightOffCommand(Light& l) : light(l) {}
    void execute() override { light.turnOff(); }
    void undo() override { light.turnOn(); }
};

// Invoker
class RemoteControl {
    std::vector<std::unique_ptr<Command>> history;
public:
    void pressButton(std::unique_ptr<Command> cmd) {
        cmd->execute();
        history.push_back(std::move(cmd));
    }
    
    void pressUndo() {
        if (!history.empty()) {
            history.back()->undo();
            history.pop_back();
        }
    }
};

Each command stores a reference to its receiver and calls the appropriate method when executed. The invoker doesn't know what action will happen - it just calls execute(). By storing commands in a history, you can easily implement undo by calling each command's undo() method.

Use Command when you need to queue operations, implement undo/redo, or decouple the sender of a request from its handler.

challenge icon

Challenge

Easy

Let's build a Text Editor with undo functionality using the Command pattern. You'll create a system where editing operations (like inserting and deleting text) are encapsulated as command objects, allowing users to execute actions and then undo them in reverse order—just like a real text editor.

You'll organize your code across four files:

  • TextDocument.h: Create the receiver class that holds and manipulates the actual text content.

    Your TextDocument class should store the document's content as a string. Implement these methods:

    • insertText(const std::string& text) — appends text to the end of the content
    • deleteText(int count) — removes the last count characters from the content (do nothing if count exceeds content length)
    • getContent() — returns the current content as a const reference
  • Command.h: Define the command interface and concrete command classes.

    Create an abstract Command class with pure virtual methods execute() and undo(), plus a virtual destructor.

    Then implement two concrete commands:

    • InsertCommand — takes a reference to a TextDocument and a string to insert. When executed, it inserts the text. When undone, it deletes the same number of characters that were inserted.
    • DeleteCommand — takes a reference to a TextDocument and a count of characters to delete. It needs to store the deleted text so it can restore it on undo. When executed, it removes characters from the end. When undone, it inserts the stored text back.
  • TextEditor.h: Create the invoker that manages command execution and history.

    Your TextEditor class should hold a reference to a TextDocument and maintain a history of executed commands using a vector of unique_ptr<Command>. Implement:

    • executeCommand(std::unique_ptr<Command> cmd) — executes the command and adds it to history
    • undo() — undoes the most recent command and removes it from history (do nothing if history is empty)
    • showContent() — prints the current document content, or [empty] if the content is empty
  • main.cpp: Demonstrate the Command pattern with undo functionality.

    Read three inputs:

    1. First text to insert (string)
    2. Second text to insert (string)
    3. Number of characters to delete (integer)

    Create a TextDocument and a TextEditor. Then perform these operations in order, showing the content after each step:

    1. Insert the first text, then show content
    2. Insert the second text, then show content
    3. Delete the specified number of characters, then show content
    4. Undo once, then show content
    5. Undo once more, then show content

For example, with inputs Hello, World, and 3:

Hello
Hello World
Hello Wo
Hello World
Hello

With inputs Code, Editor, and 6:

Code
CodeEditor
Code
CodeEditor
Code

Notice how each command knows how to reverse itself. The DeleteCommand remembers what it deleted so it can restore it, and the InsertCommand knows exactly how many characters to remove when undoing. This is the power of the Command pattern—operations become first-class objects that can be stored, executed, and reversed.

Cheat sheet

The Command pattern encapsulates a request as an object, allowing you to parameterize clients with different requests, queue operations, or support undo functionality. It decouples the object that invokes an operation from the one that knows how to perform it.

The pattern has four key components:

  • Command interface with an execute() method
  • Concrete Commands that implement specific actions
  • Receiver that performs the actual work
  • Invoker that triggers commands

Basic structure:

// Receiver - knows how to perform operations
class Light {
public:
    void turnOn() { std::cout << "Light is ON\n"; }
    void turnOff() { std::cout << "Light is OFF\n"; }
};

// Command interface
class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};

// Concrete Command
class LightOnCommand : public Command {
    Light& light;
public:
    LightOnCommand(Light& l) : light(l) {}
    void execute() override { light.turnOn(); }
    void undo() override { light.turnOff(); }
};

// Invoker
class RemoteControl {
    std::vector<std::unique_ptr<Command>> history;
public:
    void pressButton(std::unique_ptr<Command> cmd) {
        cmd->execute();
        history.push_back(std::move(cmd));
    }
    
    void pressUndo() {
        if (!history.empty()) {
            history.back()->undo();
            history.pop_back();
        }
    }
};

Each command stores a reference to its receiver and calls the appropriate method when executed. The invoker doesn't know what action will happen—it just calls execute(). By storing commands in a history, you can implement undo by calling each command's undo() method.

Use Command when you need to queue operations, implement undo/redo, or decouple the sender of a request from its handler.

Try it yourself

#include <iostream>
#include <string>
#include <memory>
#include "TextDocument.h"
#include "Command.h"
#include "TextEditor.h"

int main() {
    // Read inputs
    std::string firstText;
    std::string secondText;
    int deleteCount;
    
    std::getline(std::cin, firstText);
    std::getline(std::cin, secondText);
    std::cin >> deleteCount;

    // TODO: Create a TextDocument and a TextEditor
    
    // TODO: Perform operations in order, showing content after each:
    // 1. Insert the first text, then show content
    // 2. Insert the second text, then show content
    // 3. Delete the specified number of characters, then show content
    // 4. Undo once, then show content
    // 5. Undo once more, then show content
    
    // Hint: Use std::make_unique<InsertCommand>(...) and 
    //       std::make_unique<DeleteCommand>(...) to create commands

    return 0;
}
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