Паттерн Команда
Часть раздела Объектно-ориентированное программирование путешествия по C++ на Coddy. Урок 96 из 104.
Шаблон Command инкапсулирует запрос в виде объекта, позволяя параметризовать клиенты различными запросами, ставить операции в очередь или поддерживать функцию отмены. Он отделяет объект, вызывающий операцию, от объекта, который знает, как её выполнить.
Шаблон имеет четыре ключевых компонента: интерфейс Command с методом execute(), Concrete Commands, реализующие определённые действия, Receiver, выполняющий actual работу, и Invoker, запускающий commands:
#include <iostream>
#include <memory>
#include <vector>
// Получатель — знает, как выполнять операции
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(); }
};
// Инициатор
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 хранит ссылку на своего получателя и вызывает соответствующий метод при выполнении. Вызывающий объект не знает, какое действие произойдёт — он просто вызывает execute(). Сохраняя команды в истории, вы можете легко реализовать отмену, вызывая метод undo() каждой команды.
Используйте Command, когда вам нужно поставить операции в очередь, реализовать отмену/повтор или отделить отправителя запроса от его обработчика.
Задание
ЛегкоДавайте создадим текстовый редактор с функциональностью отмены действий с использованием паттерна Command. Вы создадите систему, в которой операции редактирования (например, вставка и удаление текста) инкапсулируются в виде объектов-команд, позволяя пользователям выполнять действия, а затем отменять их в обратном порядке, как в настоящем текстовом редакторе.
Вы организуете код в четырёх файлах:
TextDocument.h: Создайте класс-получатель, который хранит и изменяет фактическое содержимое текста.Ваш класс
TextDocumentдолжен хранить содержимое document в виде строки. Реализуйте следующие методы:insertText(const std::string& text): добавляет текст в конец содержимогоdeleteText(int count): удаляет последниеcountсимволов из содержимого (ничего не делать, если count превышает длину содержимого)getContent(): возвращает current содержимое по константной ссылке
Command.h: Определите интерфейс команды и Concrete классы команд.Создайте абстрактный класс
Commandс чистыми виртуальными методамиexecute()иundo(), а также виртуальным деструктором.Затем реализуйте две Concrete команды:
InsertCommand: принимает ссылку наTextDocumentи строку для вставки. При выполнении вставляет текст. При отмене удаляет такое же количество символов, какое было вставлено.DeleteCommand: принимает ссылку наTextDocumentи количество символов для удаления. Он должен сохранить deleted текст, чтобы восстановить его при отмене. При выполнении удаляет символы с конца. При отмене вставляет сохранённый текст обратно.
TextEditor.h: Создайте invoker, который управляет выполнением команд и историей.Ваш класс
TextEditorдолжен хранить ссылку наTextDocumentи поддерживать историю выполненных команд с помощью вектора типаunique_ptr<Command>. Реализуйте:executeCommand(std::unique_ptr<Command> cmd): выполняет команду и добавляет её в историюundo(): отменяет наиболее recent команду и удаляет её из истории (ничего не делать, если история пуста)showContent(): выводит текущее содержимое документа или[empty], если содержимое пусто
main.cpp: Продемонстрируйте паттерн Command с функциональностью отмены действий.Считайте три входных значения:
- Первый текст для вставки (строка)
- Второй текст для вставки (строка)
- Количество символов для удаления (целое число)
Создайте
TextDocumentиTextEditor. Затем выполните следующие операции по порядку, отображая содержимое после каждого шага:- Вставьте первый текст, затем отобразите содержимое
- Вставьте второй текст, затем отобразите содержимое
- Удалите указанное количество символов, затем отобразите содержимое
- Выполните отмену один раз, затем отобразите содержимое
- Выполните отмену ещё один раз, затем отобразите содержимое
Например, для входных данных Hello, World и 3:
Hello
Hello World
Hello Wo
Hello World
HelloДля входных данных Code, Editor и 6:
Code
CodeEditor
Code
CodeEditor
CodeОбратите внимание, как каждая команда знает, как отменить собственное действие. DeleteCommand запоминает, что именно она удалила, чтобы восстановить это, а InsertCommand точно знает, сколько символов нужно удалить при отмене. В этом заключается сила паттерна Command: операции становятся объектами первого класса, которые можно хранить, выполнять и отменять.
Попробуйте сами
#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Итоги — Обобщенный контейнерПотренируйтесь самостоятельно: Онлайн-компилятор C++