BehavioralC++verifiedVerified
Command Pattern in C++
Encapsulates a request as an object, allowing you to parameterize clients, queue or log requests, and support undoable operations.
How to Implement the Command Pattern in C++
1Step 1: Define the Command interface
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual void undo() = 0;
};2Step 2: Receiver
class TextEditor {
std::string text_;
public:
void insert(const std::string& s) { text_ += s; }
void deleteLast(size_t n) {
if (n <= text_.size()) text_.erase(text_.size() - n);
}
const std::string& getText() const { return text_; }
};3Step 3: Concrete commands
class InsertCommand : public Command {
TextEditor& editor_;
std::string text_;
public:
InsertCommand(TextEditor& ed, std::string text)
: editor_(ed), text_(std::move(text)) {}
void execute() override { editor_.insert(text_); }
void undo() override { editor_.deleteLast(text_.size()); }
};4Step 4: Invoker with undo stack
class CommandInvoker {
std::vector<std::unique_ptr<Command>> history_;
public:
void execute(std::unique_ptr<Command> cmd) {
cmd->execute();
history_.push_back(std::move(cmd));
}
void undo() {
if (!history_.empty()) {
history_.back()->undo();
history_.pop_back();
}
}
};
int main() {
TextEditor editor;
CommandInvoker invoker;
invoker.execute(std::make_unique<InsertCommand>(editor, "Hello"));
invoker.execute(std::make_unique<InsertCommand>(editor, " World"));
std::cout << editor.getText() << "\n"; // "Hello World"
invoker.undo();
std::cout << editor.getText() << "\n"; // "Hello"
}#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <functional>
#include <format>
#include <chrono>
#include <stdexcept>
#include <deque>
// [step] Define the Command interface with metadata
class ICommand {
public:
virtual ~ICommand() = default;
virtual void execute() = 0;
virtual void undo() = 0;
virtual std::string description() const = 0;
};
// [step] Receiver: a document model
class Document {
std::string content_;
std::string name_;
public:
explicit Document(std::string name) : name_(std::move(name)) {}
void insertAt(size_t pos, const std::string& text) {
if (pos > content_.size())
throw std::out_of_range(std::format("Position {} out of range", pos));
content_.insert(pos, text);
}
void eraseAt(size_t pos, size_t len) {
if (pos + len > content_.size())
throw std::out_of_range("Erase range out of bounds");
content_.erase(pos, len);
}
void replaceRange(size_t pos, size_t len, const std::string& text) {
eraseAt(pos, len);
insertAt(pos, text);
}
const std::string& content() const { return content_; }
const std::string& name() const { return name_; }
size_t size() const { return content_.size(); }
};
// [step] Concrete commands with full undo support
class InsertCommand : public ICommand {
Document& doc_;
size_t pos_;
std::string text_;
public:
InsertCommand(Document& doc, size_t pos, std::string text)
: doc_(doc), pos_(pos), text_(std::move(text)) {}
void execute() override { doc_.insertAt(pos_, text_); }
void undo() override { doc_.eraseAt(pos_, text_.size()); }
std::string description() const override {
return std::format("Insert '{}' at {}", text_, pos_);
}
};
class DeleteCommand : public ICommand {
Document& doc_;
size_t pos_, len_;
std::string deleted_;
public:
DeleteCommand(Document& doc, size_t pos, size_t len)
: doc_(doc), pos_(pos), len_(len) {}
void execute() override {
deleted_ = doc_.content().substr(pos_, len_);
doc_.eraseAt(pos_, len_);
}
void undo() override { doc_.insertAt(pos_, deleted_); }
std::string description() const override {
return std::format("Delete {} chars at {}", len_, pos_);
}
};
class ReplaceCommand : public ICommand {
Document& doc_;
size_t pos_, len_;
std::string newText_, oldText_;
public:
ReplaceCommand(Document& doc, size_t pos, size_t len, std::string text)
: doc_(doc), pos_(pos), len_(len), newText_(std::move(text)) {}
void execute() override {
oldText_ = doc_.content().substr(pos_, len_);
doc_.replaceRange(pos_, len_, newText_);
}
void undo() override {
doc_.replaceRange(pos_, newText_.size(), oldText_);
}
std::string description() const override {
return std::format("Replace {} chars at {} with '{}'", len_, pos_, newText_);
}
};
// [step] Command history with redo and max history
class CommandHistory {
std::deque<std::unique_ptr<ICommand>> undoStack_;
std::deque<std::unique_ptr<ICommand>> redoStack_;
size_t maxHistory_;
public:
explicit CommandHistory(size_t maxHistory = 100) : maxHistory_(maxHistory) {}
void execute(std::unique_ptr<ICommand> cmd) {
cmd->execute();
undoStack_.push_back(std::move(cmd));
redoStack_.clear();
if (undoStack_.size() > maxHistory_)
undoStack_.pop_front();
}
bool undo() {
if (undoStack_.empty()) return false;
auto cmd = std::move(undoStack_.back());
undoStack_.pop_back();
cmd->undo();
redoStack_.push_back(std::move(cmd));
return true;
}
bool redo() {
if (redoStack_.empty()) return false;
auto cmd = std::move(redoStack_.back());
redoStack_.pop_back();
cmd->execute();
undoStack_.push_back(std::move(cmd));
return true;
}
size_t undoCount() const { return undoStack_.size(); }
size_t redoCount() const { return redoStack_.size(); }
};
// [step] Demonstrate undo/redo
int main() {
Document doc("readme.txt");
CommandHistory history(50);
history.execute(std::make_unique<InsertCommand>(doc, 0, "Hello World"));
std::cout << std::format("After insert: '{}'\n", doc.content());
history.execute(std::make_unique<ReplaceCommand>(doc, 5, 6, " C++20"));
std::cout << std::format("After replace: '{}'\n", doc.content());
history.undo();
std::cout << std::format("After undo: '{}'\n", doc.content());
history.redo();
std::cout << std::format("After redo: '{}'\n", doc.content());
history.execute(std::make_unique<DeleteCommand>(doc, 0, 5));
std::cout << std::format("After delete: '{}'\n", doc.content());
std::cout << std::format("Undo={} Redo={}\n",
history.undoCount(), history.redoCount());
}Command Pattern Architecture
hourglass_empty
Rendering diagram...
lightbulb
Command Pattern in the Real World
“Think of a restaurant order ticket. A waiter (invoker) takes your order and writes it on a slip (command). The slip is handed to the kitchen (receiver) which executes it. The waiter doesn't cook anything—they just carry and deliver orders. Tickets can be queued, cancelled before cooking, or reviewed in an audit log at day's end.”