Menu
Coddy logo textTech

Паттерн Команда

Часть раздела Объектно-ориентированное программирование путешествия по 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, когда вам нужно поставить операции в очередь, реализовать отмену/повтор или отделить отправителя запроса от его обработчика.

challenge icon

Задание

Легко

Давайте создадим текстовый редактор с функциональностью отмены действий с использованием паттерна 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 с функциональностью отмены действий.

    Считайте три входных значения:

    1. Первый текст для вставки (строка)
    2. Второй текст для вставки (строка)
    3. Количество символов для удаления (целое число)

    Создайте TextDocument и TextEditor. Затем выполните следующие операции по порядку, отображая содержимое после каждого шага:

    1. Вставьте первый текст, затем отобразите содержимое
    2. Вставьте второй текст, затем отобразите содержимое
    3. Удалите указанное количество символов, затем отобразите содержимое
    4. Выполните отмену один раз, затем отобразите содержимое
    5. Выполните отмену ещё один раз, затем отобразите содержимое

Например, для входных данных 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;
}
quiz iconПроверьте себя

В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.

Все уроки раздела Объектно-ориентированное программирование

Потренируйтесь самостоятельно: Онлайн-компилятор C++