Skip to Content
Course content

139: The Command Pattern

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're sitting in a restaurant. You don't walk into the kitchen and start shouting instructions at the chef about how to sear your steak. That would be chaos. Instead, you give your order to a waiter. The waiter writes your request down on a small slip of paper and pins it to a board in the kitchen.

That little slip of paper is the secret sauce. It’s a physical object that represents a request. It doesn't matter who wrote the slip or who eventually reads it; the slip contains everything the chef needs to know to execute the task. Because the request is now an object, the kitchen can queue them, prioritize them, or even throw one away if you change your mind.

Turning Actions into Objects

In C++, we usually think of "doing something" as calling a function. But the Command Pattern asks us to stop thinking about calling functions and start thinking about creating objects that represent those functions.

Mapping this back to our restaurant:

  • The Command: The order slip. In code, this is an abstract base class with an execute() method.
  • The Receiver: The chef. This is the object that actually knows how to perform the work (e.g., the Light or AirConditioner class).
  • The Invoker: The waiter or the order board. This is the object that triggers the command, but doesn't need to know what the command actually does.
  • The Client: You, the customer. You're the one who decides which command to create and associates it with a receiver.

Building a Smart Home Remote

Let's look at how this actually looks in code. Suppose we're building a smart home system. We don't want our remote control class to have a hard-coded dependency on every single appliance in the house. That would be a maintenance nightmare.

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

// The Receiver: This is the thing being controlled
class Light {
public:
    void on() { std::cout < "The light is bright!" < std::endl; }
    void off() { std::cout < "The light is off." < std::endl; }
};

// The Command Interface
class Command {
public:
    virtual ~Command() = default;
    virtual void execute() = 0;
    virtual void undo() = 0; // Commands make undoing trivial
};

// Concrete Command for turning on the light
class LightOnCommand : public Command {
    Light* light;
public:
    LightOnCommand(Light* l) : light(l) {}
    void execute() override { light->on(); }
    void undo() override { light->off(); }
};

// The Invoker: The Remote Control
class RemoteControl {
    std::unique_ptr<Command> slot;
public:
    void setCommand(std::unique_ptr<Command> cmd) {
        slot = std::move(cmd);
    }
    void pressButton() {
        if (slot) slot->execute();
    }
    void pressUndo() {
        if (slot) slot->undo();
    }
};

I love this approach because the RemoteControl is now completely agnostic. It doesn't know Light exists. It just knows it has a Command object, and that object has an execute() method. If you add a GarageDoor or a CoffeeMaker tomorrow, you don't have to change a single line of code inside the RemoteControl class.

The Power of the Command History

The real "aha!" moment with this pattern comes when you realize that since commands are objects, you can store them in a list. If you push every executed command onto a std::stack, you've just implemented a professional-grade Undo system. You just pop the last command and call its undo() method.

It's a bit more boilerplate upfront—you have to write a class for every action—but the decoupling is worth it. I've seen projects where the "Invoker" was a complex GUI menu and the "Receiver" was a database engine; keeping them separate saved us from a total rewrite when the database API changed.




📋 Practical Task

Implementing a Command-Based Text Editor Undo Stack

You are building a simplified text editor. Instead of just changing a string, you need to implement a system that allows the user to undo their last action.

Your Task:

  1. Create a Document class (the Receiver) that holds a std::string and has methods to insertText(string text) and deleteLastChar().
  2. Implement a Command base class and two concrete commands: InsertCommand and DeleteCommand.
  3. Each command must implement execute() and undo(). (Hint: To undo an insertion, you'll need to remove the text that was just added).
  4. Create an Editor class (the Invoker) that maintains a std::vector or std::stack of executed commands.
  5. Implement a runCommand(std::unique_ptr<Command> cmd) method that executes the command and stores it, and an undo() method that pops the last command and calls its undo logic.

Test your implementation by inserting "Hello ", then inserting "World", then calling undo twice to return the document to an empty state.

Rating
0 0

There are no comments for now.

to be the first to leave a comment.