Command

Паттерн, превращающий действие в объект — чтобы его можно было передать, поставить в очередь, залогировать и отменить.

Command инкапсулирует запрос в виде объекта, отделяя отправителя действия от исполнителя и позволяя ставить запросы в очередь, логировать и отменять их.

Какую задачу решает

Кнопка «Сохранить» должна вызвать сохранение. Но если зашить вызов прямо в кнопку, её не переиспользовать и не отменить действие. Command превращает «что сделать» в объект с методом execute() (и часто undo()). Теперь действие можно хранить в истории, ставить в очередь, повторять и откатывать.

Идея и реализация

Реализуем редактор текста с командами и историей отмены.

class Editor:
    def __init__(self):
        self.text = ""


class AppendCommand:
    def __init__(self, editor, value):
        self.editor = editor
        self.value = value

    def execute(self):
        self.editor.text += self.value

    def undo(self):
        self.editor.text = self.editor.text[:-len(self.value)]


class History:
    def __init__(self):
        self._done = []

    def run(self, command):
        command.execute()
        self._done.append(command)

    def undo(self):
        if self._done:
            self._done.pop().undo()


editor = Editor()
history = History()
history.run(AppendCommand(editor, "Привет"))
history.run(AppendCommand(editor, ", мир"))
print(editor.text)

history.undo()                  # откат последней команды
print(editor.text)

Вывод:

Привет, мир
Привет

Каждое действие — отдельный объект, который умеет себя выполнить и откатить. История хранит стек выполненных команд; undo() снимает верхнюю и вызывает её откат. Отправитель (кнопка/история) не знает деталей — он работает с интерфейсом команды.

Ключевая ценность — развязка во времени и пространстве. Раз действие стало объектом, момент его создания и момент выполнения можно развести: команду создают здесь и сейчас, а исполняют позже, в другом потоке или вообще на другой машине (если команду сериализовать). Так Command становится основой очередей задач: продюсер кладёт команды в очередь, воркеры достают и вызывают execute(), ничего не зная об источнике.

Для полноценного redo достаточно вести второй стек: при undo снятую команду кладём в «отменённые», при redo — выполняем верхнюю из него заново. А если откат сложного действия нельзя выразить обратной операцией, команду часто комбинируют с паттерном Memento: перед выполнением она сохраняет снимок состояния, а в undo() просто восстанавливает его.

Плюсы и минусы

  • Плюс: undo/redo, очереди задач, отложенное и повторное выполнение, логирование действий «из коробки».
  • Минус: на каждое действие — класс; для простых случаев избыточно (в Python спасают функции/замыкания).

Где встречается

Undo/redo в редакторах, очереди фоновых задач (Celery-подобные), макросы (последовательность команд), транзакции, кнопки и горячие клавиши в GUI, паттерн «job/task» в системах обработки.

Итог

  • Command превращает запрос в объект с execute() и часто undo().
  • Развязывает отправителя и исполнителя.
  • Даёт историю, очереди, отмену и повтор действий.
Проверьте себя
1. Что Command превращает в объект?
AКласс
BЗапрос/действие (с методом execute)
CСписок
DПодписчика
2. Какую возможность естественно даёт Command?
AГарантию одного экземпляра
BОтмену и повтор действий (undo/redo), очереди, логирование
CЭкономию памяти
DАдаптацию интерфейсов
3. Что обычно хранит история команд для отмены?
AТолько последний результат
BСтек выполненных команд, у каждой есть undo()
CИмя пользователя
DОдин синглтон
Поддержать проект