Command (Команда)¶
Категория: паттерн поведения.
Проблема¶
Действие пользователя (нажатие кнопки, пункт меню, горячая клавиша) нужно не просто выполнить, а: - поставить в очередь и выполнить позже; - сохранить в истории и дать возможность отменить (undo) или повторить (redo); - передать как параметр, не привязываясь к тому, что конкретно вызывающий код должен знать о деталях действия.
Если реализовать действие как обычный вызов метода получателя, ни одно из этих требований не выполняется - вызов метода происходит сразу и бесследно, его нельзя сохранить как объект и передать дальше.
Решение¶
- Каждое действие оформляется отдельным объектом-командой с общим интерфейсом (обычно - метод
Execute(), часто вместе сUndo()). - Команда хранит внутри всё необходимое для выполнения действия: ссылку на получателя (объект, который реально что-то делает) и параметры операции.
- Инициатор (например, кнопка UI или обработчик очереди задач) работает только с абстракцией
ICommandи вызываетExecute(), не зная деталей конкретного действия. - Благодаря тому, что команда - полноценный объект, её можно сохранить в списке (история для undo/redo), поставить в очередь, сериализовать или передать по сети.
Структура¶
Command- интерфейс с методомExecute()(и опциональноUndo()).ConcreteCommand- хранит получателя и параметры, реализуетExecute/Undoчерез вызовы методов получателя.Receiver- объект, который реально выполняет действие; ничего не знает о существовании команд.Invoker- хранит и запускает команды (и, возможно, историю для отмены), не зная их внутреннего устройства.
Когда применять¶
- Нужна отмена/повтор действий (undo/redo) - для этого само действие обязано быть объектом, а не просто вызовом метода.
- Действия нужно ставить в очередь, откладывать во времени или выполнять асинхронно.
- Нужно логировать все выполненные операции или строить на их основе транзакционность (например, отменить всю пачку команд при ошибке).
- Один и тот же UI-элемент (кнопка, пункт меню) должен запускать разные действия, подставляемые снаружи.
Плюсы¶
- Отделяет объект, инициирующий действие, от объекта, который его выполняет.
- Позволяет собирать историю команд для undo/redo, логирования или отложенного выполнения.
- Новое действие добавляется новым классом-командой, без изменения инициатора.
Минусы¶
- Для простого, разового действия без нужды в отмене или очереди - создание отдельного класса-команды избыточно; в C# такие случаи обычно проще выразить делегатом (
Action). - Полноценная реализация Undo (особенно для составных операций) может потребовать хранить существенный объём состояния "до" изменения, что увеличивает сложность команд.
Пример в .NET Framework / BCL¶
ICommandв WPF/MAUI (RelayCommand,DelegateCommand) - связывает элементы UI (кнопки, пункты меню) с обработчиками, включая поддержкуCanExecute.- Паттерн лежит в основе очередей задач и джобов (например, в фоновых обработчиках, где "команда" сериализуется, кладётся в очередь и выполняется позже, возможно, в другом процессе).
- Транзакции с поддержкой отмены в редакторах (текстовых, графических, IDE) - именно так реализован Undo/Redo в большинстве таких приложений.