Паттерн «Команда» (Command). Описание и примеры использования

Команда (Command) – один из «классических» поведенческих паттернов, описанных ещё у «Банды Четырёх» [1]. Он используется для создания гибкого механизма действий над чем-либо или команд. В этом механизме класс отправитель команды и класс получатель не зависят друг от друга.

В данном паттерне объект «Команды» инкапсулирует как само действие, так и его параметры.

Описание и сфера применения

Схема паттерна комманда

Паттерн «Команда» состоит из следующих компонентов:

  • Command (Команда)
    Базовый класс. Объявляет интерфейс для выполняемой операции (команды);
  • ConcretteCommand (Конкретная команда)
    Определяет связь между объектом получателем (Receiver) и действием. Реализует интерфейс, объявленный Command;
  • Client (Клиент)
    Создаёт объект ConcretteCommand и задаёт для него получателя;
  • Invoker (Инициатор)
    Обращается к команде для выполнения операции;
  • Receiver (Получатель)
    Располагает информацией о способах выполнения команды. В его роли может выступить любой класс.

Паттерн «Команда» может пригодиться в следующих областях [2].

  • GUI (в основном, кнопки пользовательского интерфейса и пункты меню);
  • Запись макросов;
  • Многоуровневая отмена операций (Undo);
  • Сети;
  • Индикаторы выполнения;
  • Пулы потоков;
  • Транзакции;
  • Мастера (мастера настроек, установки программ и т.п.).
Пример «классической» реализации на C#

Рассмотрим следующую программу, которая будет в самых общих чертах моделировать «управление» самолётом.

Получателем команд будет самолёт, и он будет получать две команды: «Взлёт» (TakeOff) и «Посадка» (Landing).

Состояние самолёта (полёт или пребывание на земле) будет задаваться обычной текстовой строкой. Чтобы не усложнять восприятие примера и без того не самого простого паттерна, паттерн «Состояние» применять не станем.

Ниже приведён код класса самолёта (Receiver).

Так как у обеих команд общим является не только интерфейс, но также Receiver и механизм инициализации, вместо интерфейса в качестве основы используем обычный абстрактный класс.

Команда «Взлёт»:

Команда «Посадка»:

В обеих командах реализована одноуровневая отмена действия.

Класс инициализатор команд (Invoker):

Методы Run и Undo запускают на выполнение и отменяют выполненную команду соответственно. Сама команда задаётся через свойство Command, которое связано с закрытым полем _command.

Далее приведена консольная программа, которая наглядно иллюстрирует работу паттерна.

Управление программой производится цифровыми символами. Программа обрабатывает код нажатой клавиши. При вводе цифры «1» самолёт «взлетает». При вводе цифры «2» самолёт «садится». При вводе цифры «3» происходит отмена выполненной команды. Цифра «4» — завершение работы программы.

Корректное выполнение команд в Invoker

Пример, который был приведён выше вполне рабочий, но в нём есть один существенный недочёт. Что произойдёт при вызове метода Run или Undo, если команда по тем или иным причинам не была своевременно задана или инициализирована?

Правильный ответ – исключение. Ведь в этом случае мы будем работать с несуществующим объектом.

Поэтому, при написании рабочих проектов в Invoker необходимо обязательно проверять существование объекта команды, и ни в коем случае не стоит забывать о защитном программировании (например, команда по умолчанию) и обработке тех же исключений.

Если не получается корректно разрешить эту проблему локально с помощью защитного программирования, создайте собственный класс исключения, которое будет выдавать Invoker в случае, когда объект команды не инициализирован (вы и тем более другой человек, который будет работать с вашим кодом должны понимать из-за чего именно в программе произошёл сбой).

Привести примеры для всех возможных вариантов. Но, для случая с командой по умолчанию реализации метода Run в Invoker должна выглядеть примерно так:

А, для случая с исключением примерно так:

Использование вырожденного паттерна «Команда» в GUI (на примере C++ Qt)

Рассмотрим пример использования паттерна «Команда» для графического интерфейса (GUI). Для этого напишем простую программу с интерфейсом из двух кнопок.

Программа

Каждая из кнопок создаёт объекты команд и запускает их на выполнение.

Программу будем писать на языке программирования C++ с использованием библиотеки Qt 5.8.

Первая команда будет отображать стандартное окно с информацией о библиотеке Qt, а вторая также стандартный диалог с текстовым сообщением (своего рода аналог MessageBox.Show() в C#). Оба окна должны располагаться по центру относительно родительского окна.

Сами команды и Invoker не представляют ничего особенного.

Базовый класс команд:

Первая команда:

Вторая команда:

Invoker:

Все вышеприведённые компоненты паттерна не отличаются от «классической» версии. Но, совсем другое дело Receiver и Client.

В предыдущем примере роль Receiver играл объект класса Airplane, а в качестве клиента выступало приложение. Здесь же Receiver как самостоятельный элемент будет отсутствовать, а его функции вместе с функциями клиента возьмёт на себя родительское окно.

Дело в том, что в Qt для того, чтобы расположить окно с сообщением по центру родительского окна, необходимо передать родительское окно в качестве параметра соответствующего статического метода класса QMessageBox (как это показано в коде классов команд).

Так как система отображения диалоговых окон при помощи класса QMessageBox достаточно унифицирована, а родительские окна могут быть произвольными, то при использовании паттерна «Команда» очень удобно передавать родительское в качестве Receiver’а, хотя «настоящим» Receiver’ом является скорее QMessageBox.

Ниже приведён код главного (и единственного) окна рассматриваемой программы.

Работа программы на примере диалога с текстовым сообщением:

Работа программы

Мы рассмотрели частный случай реализации паттерна «Команда». Это один из исключительных примеров, когда вырожденный паттерн оказывается практичнее «классического» варианта.

Нередко для того чтобы получить эффективное решение недостаточно иметь готовый «сборник рецептов» и нужно пофантазировать над задачей.

Однако к нестандартным решениям следует всё же подходить обдуманно и ни в коем случае не злоупотреблять. Иначе велик шанс, например, изобрести очередной «велосипед с квадратными колёсами».

Источники:
  1. Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приёмы объектно-ориентированного проектирования. Паттерны проектирования.
  2. Команда (шаблон проектирования) (Википедия).
  3. Шлее М. Qt 5.3. Профессиональное программирование на C++.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *