Команда (Command)

Метафора

 

Паттерн команда хорошо может быть представлен в виде выключателя в квартирах. Каждый выключатель по своей сути делает одно простое действие — соединяет или разъединяет провода. Что будет при этом ему не важно, к нему может быть подключена лампочка или пылесос, для выключателя это не имеет значения.

 

Назначение

 

Паттерн преобразовывает запрос на выполнение действия в отдельный объект-команду. Такая инкапсуляция позволяет передавать эти действия другим объектам в качестве параметра, приказывая им выполнить запрошенную операцию. Команда – это объект, поэтому над ней допустимы любые операции, что и над объектом.

Основное назначение паттерна – отвязать источник действия от места его исполнения.

Типичный пример — проектирование пользовательского интерфейса. Пункт меню не должен знать, что происходит при его активизации пользователем (чтобы не вводить дополнительные зависимости с объектами бизнес-логики), он должен знать лишь о некотором действии, которое нужно выполнить при нажатии кнопки.

Преимущества:

  • Обеспечивает обработку команды в виде объекта, что позволяет сохранять её, передавать в качестве параметра методам, а также возвращать её в виде результата, как и любой другой объект.

Недостатки:

  • Усложнение общей архитектуры.

 

Диаграмма

 

Команда

Команда

 

Порядок работы с шаблоном следующий:

  1. Клиентом создаётся некоторая команда, команда получает ссылку на receiver и уже внутри себя знает, какое действие необходимо из него вызвать.
  2. Команда кладётся в некоторый список команд или свойство Invoker’a (пункт меню)
  3. Пользователь нажимает на пункт меню, после чего invoker вызывает операцию команды – execute().

Существуют две версии реализации паттерна, в одной команда сама по себе выполняет операцию, в другой (GoF версия) версии команда не выполняет действие самостоятельно, но знает того, кто может это сделать (Receiver) и делегирует вызов действия через него. Клиент создает экземпляр конкретной команды и конфигурирует её получателем.

Данный паттерн по своей сути напоминает callback функцию в ООП обёртке.

 

Пример

 

Рассмотрим небольшой пример. Предположим мы разрабатываем класс lcl_switch который включает (выключает) лампочку – lcl_light:

В конструкторе нам поступает lcl_light, при нажатии на «вкл» мы его включаем, при нажатии на «выкл» — выключаем.

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

К сожалению, данное решение не обладает гибкостью, т.к. при добавлении нового устройства мы будем вынуждены дублировать поведение переключателя.

Воспользуемся шаблоном команда. Создадим интерфейс команды:

Создадим отдельные классы-команды на включение, выключение устройств. Некоторые из них:

Теперь у нас есть классы, которые могут что-то включать и выключать, изменим наш класс-переключатель и сделаем его независимым от типа устройств:

Теперь наш переключатель не содержит ссылок на конкретные устройства, вместо этого ему на вход поступают две команды на включение и отключение, которые что-то выполняют. При создании нового прибора нам будет достаточно определить новые команды включения и отключения и снабдить ими наш переключатель.

Бывают случаи, когда выключатели работают с целым набором устройств, для того чтобы реализовать нечто подобное, создадим следующий класс:

Вместо обычных команд в переключатель мы теперь можем подавать составную команду, которая внутри себя содержит перечень других (для включения/отключения группы устройств), при этом наш класс переключатель не почувствует никакой разницы.