C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
139: The Command Pattern
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
LightorAirConditionerclass). - 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:
- Create a
Documentclass (the Receiver) that holds astd::stringand has methods toinsertText(string text)anddeleteLastChar(). - Implement a
Commandbase class and two concrete commands:InsertCommandandDeleteCommand. - Each command must implement
execute()andundo(). (Hint: To undo an insertion, you'll need to remove the text that was just added). - Create an
Editorclass (the Invoker) that maintains astd::vectororstd::stackof executed commands. - Implement a
runCommand(std::unique_ptr<Command> cmd)method that executes the command and stores it, and anundo()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.
There are no comments for now.