Паттерн Команда
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy — урок 96 из 104.
Паттерн Команда (Command) инкапсулирует запрос в виде объекта, позволяя параметризовать клиентов различными запросами, ставить операции в очередь или поддерживать функциональность отмены. Он отделяет объект, инициирующий операцию, от объекта, который непосредственно знает, как её выполнить.
Паттерн состоит из четырех ключевых компонентов: интерфейс Command с методом execute(), Concrete Commands, которые реализуют определенные действия, Receiver, который выполняет фактическую работу, и Invoker, который запускает команды:
#include <iostream>
#include <memory>
#include <vector>
// Получатель (Receiver) — знает, как выполнять операции
class Light {
public:
void turnOn() { std::cout << "Light is ON\n"; }
void turnOff() { std::cout << "Light is OFF\n"; }
};
// Интерфейс команды
class Command {
public:
virtual void execute() = 0;
virtual void undo() = 0;
virtual ~Command() = default;
};
// Конкретные команды
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();
}
}
};Каждая команда хранит ссылку на своего получателя и вызывает соответствующий метод при выполнении. Инициатор не знает, какое действие произойдет — он просто вызывает execute(). Сохраняя команды в истории, вы можете легко реализовать отмену, вызывая метод undo() каждой команды.
Используйте паттерн Команда, когда вам нужно ставить операции в очередь, реализовать отмену/повтор действий или отделить отправителя запроса от его обработчика.
Задание
ЛегкоДавайте создадим Текстовый редактор с функцией отмены (undo), используя паттерн Команда (Command). Вы создадите систему, в которой операции редактирования (такие как вставка и удаление текста) инкапсулированы в объекты команд, что позволяет пользователям выполнять действия, а затем отменять их в обратном порядке — совсем как в настоящем текстовом редакторе.
Вы организуете свой код в четырех файлах:
TextDocument.h: Создайте класс-получатель (receiver), который хранит и манипулирует фактическим текстовым содержимым.Ваш класс
TextDocumentдолжен хранить содержимое документа в виде строки. Реализуйте следующие методы:insertText(const std::string& text)— добавляет текст в конец содержимогоdeleteText(int count)— удаляет последниеcountсимволов из содержимого (ничего не делайте, если count превышает длину содержимого)getContent()— возвращает текущее содержимое как константную ссылку
Command.h: Определите интерфейс команды и конкретные классы команд.Создайте абстрактный класс
Commandс чисто виртуальными методамиexecute()иundo(), а также виртуальным деструктором.Затем реализуйте две конкретные команды:
InsertCommand— принимает ссылку наTextDocumentи строку для вставки. При выполнении вставляет текст. При отмене удаляет то же количество символов, которое было вставлено.DeleteCommand— принимает ссылку наTextDocumentи количество символов для удаления. Она должна сохранять удаленный текст, чтобы иметь возможность восстановить его при отмене. При выполнении удаляет символы с конца. При отмене вставляет сохраненный текст обратно.
TextEditor.h: Создайте инициатор (invoker), который управляет выполнением команд и историей.Ваш класс
TextEditorдолжен хранить ссылку наTextDocumentи вести историю выполненных команд, используя векторunique_ptr<Command>. Реализуйте:executeCommand(std::unique_ptr<Command> cmd)— выполняет команду и добавляет ее в историюundo()— отменяет последнюю команду и удаляет ее из истории (ничего не делайте, если история пуста)showContent()— выводит текущее содержимое документа или[empty], если содержимое пусто
main.cpp: Продемонстрируйте работу паттерна Команда с функцией отмены.Прочитайте три входных значения:
- Первый текст для вставки (строка)
- Второй текст для вставки (строка)
- Количество символов для удаления (целое число)
Создайте
TextDocumentиTextEditor. Затем выполните следующие операции по порядку, отображая содержимое после каждого шага:- Вставьте первый текст, затем покажите содержимое
- Вставьте второй текст, затем покажите содержимое
- Удалите указанное количество символов, затем покажите содержимое
- Выполните отмену один раз, затем покажите содержимое
- Выполните отмену еще раз, затем покажите содержимое
Например, при входных данных Hello, World и 3:
Hello
Hello World
Hello Wo
Hello World
HelloПри входных данных Code, Editor и 6:
Code
CodeEditor
Code
CodeEditor
CodeОбратите внимание, как каждая команда знает, как отменить саму себя. DeleteCommand запоминает, что она удалила, чтобы иметь возможность это восстановить, а InsertCommand точно знает, сколько символов нужно удалить при отмене. В этом и заключается сила паттерна Команда — операции становятся объектами первого класса, которые можно хранить, выполнять и отменять.
Шпаргалка
Паттерн Команда (Command) инкапсулирует запрос в виде объекта, позволяя параметризовать клиенты различными запросами, ставить операции в очередь или поддерживать функциональность отмены. Он отделяет объект, инициирующий операцию, от объекта, который знает, как её выполнить.
Паттерн состоит из четырех ключевых компонентов:
- Интерфейс Command с методом
execute() - Конкретные команды (Concrete Commands), которые реализуют определенные действия
- Получатель (Receiver), который выполняет фактическую работу
- Инициатор (Invoker), который запускает команды
Базовая структура:
// Получатель — знает, как выполнять операции
class Light {
public:
void turnOn() { std::cout << "Light is ON\n"; }
void turnOff() { std::cout << "Light is OFF\n"; }
};
// Интерфейс команды
class Command {
public:
virtual void execute() = 0;
virtual void undo() = 0;
virtual ~Command() = default;
};
// Конкретная команда
class LightOnCommand : public Command {
Light& light;
public:
LightOnCommand(Light& l) : light(l) {}
void execute() override { light.turnOn(); }
void undo() override { light.turnOff(); }
};
// Инициатор
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();
}
}
};Каждая команда хранит ссылку на своего получателя и вызывает соответствующий метод при выполнении. Инициатор не знает, какое действие произойдет — он просто вызывает execute(). Сохраняя команды в истории, вы можете реализовать отмену, вызывая метод undo() каждой команды.
Используйте паттерн Команда, когда вам нужно ставить операции в очередь, реализовать отмену/повтор действий или отделить отправителя запроса от его обработчика.
Попробуйте сами
#include <iostream>
#include <string>
#include <memory>
#include "TextDocument.h"
#include "Command.h"
#include "TextEditor.h"
int main() {
// Чтение входных данных
std::string firstText;
std::string secondText;
int deleteCount;
std::getline(std::cin, firstText);
std::getline(std::cin, secondText);
std::cin >> deleteCount;
// TODO: Создайте TextDocument и TextEditor
// TODO: Выполните операции по порядку, отображая содержимое после каждой:
// 1. Вставьте первый текст, затем выведите содержимое
// 2. Вставьте второй текст, затем выведите содержимое
// 3. Удалите указанное количество символов, затем выведите содержимое
// 4. Отмените действие один раз, затем выведите содержимое
// 5. Отмените действие еще раз, затем выведите содержимое
// Подсказка: Используйте std::make_unique<InsertCommand>(...) и
// std::make_unique<DeleteCommand>(...) для создания команд
return 0;
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыСборка и компиляция C++Заголовочные файлы и файлы исходного кодаПространства имен и область видимостиВведение в ООП на C++Классы и объектыУказатель 'this'Методы (функции-члены)Атрибуты (члены данных)Основы конструкторов и деструкторовИтоги — Простой калькулятор4Свойства классов
Члены экземпляра против статическихГеттеры и сеттерыКонстантные функции-членыКлючевое слово mutableСтатические методы и переменныеДружественные функции и классыИтоги — Менеджер банковских счетов7Наследование
Основы наследованияУровни доступа при наследованииПорядок вызова конструкторов и деструкторовПереопределение методовВиртуальные функции и VTableМножественное наследованиеВиртуальное наследованиеИтоги — Иерархия сотрудников10Обзор STL
Обзор и философия STLКонтейнеры STLИтераторыАлгоритмы STLФункторы и лямбда-выраженияИтоги: частота слов13Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonFactory и Abstract FactoryПаттерн BuilderПаттерн ObserverПаттерн Strategy2Управление памятью
Стек и кучаУказатели и ссылкиДинамическая память (new/delete)Умные указатели в C++RAII в C++Итоги — Менеджер динамического массива5Инкапсуляция
Спецификаторы доступа в C++Спецификаторы доступа: подробный разборСокрытие данныхStruct против ClassВложенные и внутренние классыИтоги — Система учета студентов8Полиморфизм
Полиморфизм компиляции и времени выполненияПерегрузка функцийВиртуальные функции: повторениеЧистые виртуальные функцииАбстрактные классыПроектирование интерфейсов на C++Dynamic Casting и RTTIИтоги: Калькулятор фигур11Продвинутые концепции ООП
Композиция против наследованияМиксины через CRTPИдиома PimplСтирание типовEnum Classes и строгая типизацияОбработка исключений в ООППользовательские иерархии исключений14Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикRAII как паттерн3Конструкторы и деструкторы
Конструктор по умолчаниюПараметризованный конструкторКонструктор копированияКонструктор перемещенияСписки инициализации конструктораДелегирующие конструкторыГлубокое погружение в деструкторыПравило трех / пяти / нуляПовторение — класс String6Перегрузка операторов
Введение в перегрузку операторовПерегрузка арифметических операторовПерегрузка операторов сравненияОператоры потоковПерегрузка оператора присваиванияПерегрузка операторов [] и ()Операторы преобразования типовИтоги — класс Matrix9Шаблоны
Шаблоны функцийШаблоны классовСпециализация шаблоновВариативные шаблоныОсновы SFINAE и Type TraitsИтоги — Обобщенный контейнер