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
EasyLet'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
TextDocumentclass should store the document's content as a string. Implement these methods:insertText(const std::string& text)— appends text to the end of the contentdeleteText(int count)— removes the lastcountcharacters 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
Commandclass with pure virtual methodsexecute()andundo(), plus a virtual destructor.Then implement two concrete commands:
InsertCommand— takes a reference to aTextDocumentand 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 aTextDocumentand 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
TextEditorclass should hold a reference to aTextDocumentand maintain a history of executed commands using a vector ofunique_ptr<Command>. Implement:executeCommand(std::unique_ptr<Command> cmd)— executes the command and adds it to historyundo()— 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:
- First text to insert (string)
- Second text to insert (string)
- Number of characters to delete (integer)
Create a
TextDocumentand aTextEditor. Then perform these operations in order, showing the content after each step:- Insert the first text, then show content
- Insert the second text, then show content
- Delete the specified number of characters, then show content
- Undo once, then show content
- Undo once more, then show content
For example, with inputs Hello, World, and 3:
Hello
Hello World
Hello Wo
Hello World
HelloWith inputs Code, Editor, and 6:
Code
CodeEditor
Code
CodeEditor
CodeNotice 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;
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesC++ Build & CompilationHeader Files & Source FilesNamespaces & ScopeIntroduction to OOP in C++Classes vs ObjectsThe 'this' PointerMethods (Member Functions)Attributes (Data Members)Ctors & Dtors BasicsRecap - Simple Calculator4Class Properties
Instance vs Static MembersGetters and SettersConst Member FunctionsMutable KeywordStatic Methods and VariablesFriend Functions & ClassesRecap - Bank Account Manager7Inheritance
Basic InheritanceInheritance Access LevelsCtor & Dtor Call OrderMethod OverridingVirtual Functions & VTableMultiple InheritanceVirtual InheritanceRecap - Employee Hierarchy2Memory Management
Stack vs Heap MemoryPointers and ReferencesDynamic Memory (new/delete)Smart Pointers in C++RAII in C++Recap - Dynamic Array Manager5Encapsulation
Access Specifiers in C++Access Specifiers In DepthInformation HidingStruct vs ClassNested & Inner ClassesRecap - Student Records System8Polymorphism
Compile vs Runtime PolymorphFunction OverloadingVirtual Functions RevisitedPure Virtual FunctionsAbstract ClassesInterface Design in C++Dynamic Casting & RTTIRecap - Shape Calculator11Advanced OOP Concepts
Composition vs InheritanceMixins via CRTPPimpl IdiomType ErasureEnum Classes & Strong TypingException Handling in OOPCustom Exception Hierarchies14Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite PatternRAII as a Pattern3Constructors & Destructors
Default ConstructorParameterized ConstructorCopy ConstructorMove ConstructorConstructor Init ListsDelegating ConstructorsDestructor Deep DiveRule of Three / Five / ZeroRecap - String Class6Operator Overloading
Intro to Operator OverloadArithmetic Operator OverloadComparison Operator OverloadStream OperatorsAssignment Operator Overload[] and () Operator OverloadType Conversion OperatorsRecap - Matrix Class9Templates
Function TemplatesClass TemplatesTemplate SpecializationVariadic TemplatesSFINAE & Type Traits BasicsRecap - Generic Container