Метафора
Паттерн команда хорошо может быть представлен в виде выключателя в квартирах. Каждый выключатель по своей сути делает одно простое действие — соединяет или разъединяет провода. Что будет при этом ему не важно, к нему может быть подключена лампочка или пылесос, для выключателя это не имеет значения.
Назначение
Паттерн преобразовывает запрос на выполнение действия в отдельный объект-команду. Такая инкапсуляция позволяет передавать эти действия другим объектам в качестве параметра, приказывая им выполнить запрошенную операцию. Команда – это объект, поэтому над ней допустимы любые операции, что и над объектом.
Основное назначение паттерна – отвязать источник действия от места его исполнения.
Типичный пример — проектирование пользовательского интерфейса. Пункт меню не должен знать, что происходит при его активизации пользователем (чтобы не вводить дополнительные зависимости с объектами бизнес-логики), он должен знать лишь о некотором действии, которое нужно выполнить при нажатии кнопки.
Преимущества:
- Обеспечивает обработку команды в виде объекта, что позволяет сохранять её, передавать в качестве параметра методам, а также возвращать её в виде результата, как и любой другой объект.
Недостатки:
- Усложнение общей архитектуры.
Диаграмма
Порядок работы с шаблоном следующий:
- Клиентом создаётся некоторая команда, команда получает ссылку на receiver и уже внутри себя знает, какое действие необходимо из него вызвать.
- Команда кладётся в некоторый список команд или свойство Invoker’a (пункт меню)
- Пользователь нажимает на пункт меню, после чего invoker вызывает операцию команды – execute().
Существуют две версии реализации паттерна, в одной команда сама по себе выполняет операцию, в другой (GoF версия) версии команда не выполняет действие самостоятельно, но знает того, кто может это сделать (Receiver) и делегирует вызов действия через него. Клиент создает экземпляр конкретной команды и конфигурирует её получателем.
Данный паттерн по своей сути напоминает callback функцию в ООП обёртке.
Пример
Рассмотрим небольшой пример. Предположим мы разрабатываем класс lcl_switch который включает (выключает) лампочку – lcl_light:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
CLASS lcl_switch DEFINITION. PRIVATE SECTION. DATA: mo_light TYPE REF TO lcl_light. PUBLIC SECTION. METHODS: constructor IMPORTING io_light TYPE REF TO lcl_light, flip_up, flip_down. ENDCLASS. CLASS lcl_switch IMPLEMENTATION. METHOD constructor. mo_light = io_light. ENDMETHOD. METHOD flip_up. mo_light->turn_on( ). ENDMETHOD. METHOD flip_down. mo_light->turn_off( ). ENDMETHOD. ENDCLASS. |
В конструкторе нам поступает lcl_light, при нажатии на «вкл» мы его включаем, при нажатии на «выкл» — выключаем.
Предположим, нам понадобилось создать некоторый подобный класс, но уже для работы с телевизором, первое, что приходит в голову сделать нечто подобное:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
CLASS lcl_switch_tv DEFINITION. PRIVATE SECTION. DATA: mo_tv TYPE REF TO lcl_tv. PUBLIC SECTION. METHODS: constructor IMPORTING io_tv TYPE REF TO lcl_tv, flip_up, flip_down. ENDCLASS. CLASS lcl_switch_tv IMPLEMENTATION. METHOD constructor. mo_tv = io_tv. ENDMETHOD. METHOD flip_up. mo_tv->turn_on_tv( ). ENDMETHOD. METHOD flip_down. mo_tv->turn_off_tv( ). ENDMETHOD. ENDCLASS. |
К сожалению, данное решение не обладает гибкостью, т.к. при добавлении нового устройства мы будем вынуждены дублировать поведение переключателя.
Воспользуемся шаблоном команда. Создадим интерфейс команды:
1 2 3 4 |
INTERFACES lif_command. METHODS: execute. ENDINTERFACE. |
Создадим отдельные классы-команды на включение, выключение устройств. Некоторые из них:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
CLASS lcl_tv_on_command DEFINITION. PRIVATE SECTION. DATA: mo_tv TYPE REF TO lcl_tv. PUBLIC SECTION. INTERFACES: lif_command. METHODS: constructor IMPORTING io_tv TYPE REF TO lcl_tv. ENDCLASS. CLASS lcl_tv_on_command IMPLEMENTATION. METHOD constructor. mo_tv = io_tv. ENDMETHOD. METHOD lif_command~execute. mo_tv->turn_on_tv( ). ENDMETHOD. ENDCLASS. CLASS lcl_light_off_command DEFINITION. PRIVATE SECTION. DATA: mo_light TYPE REF TO lcl_light. PUBLIC SECTION. INTERFACES: lif_command. METHODS: constructor IMPORTING io_light TYPE REF TO lcl_light. ENDCLASS. CLASS lcl_light_off_command IMPLEMENTATION. METHOD constructor. mo_light = io_light. ENDMETHOD. METHOD lif_command~execute. mo_light->turn_off( ). ENDMETHOD. ENDCLASS. |
Теперь у нас есть классы, которые могут что-то включать и выключать, изменим наш класс-переключатель и сделаем его независимым от типа устройств:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
CLASS lcl_switch DEFINITION. PRIVATE SECTION. DATA: mo_on_command TYPE REF TO lif_command, mo_off_command TYPE REF TO lif_command. PUBLIC SECTION. METHODS: constructor IMPORTING io_on TYPE REF TO lif_command io_off TYPE REF TO lif_command, flip_up, flip_down. ENDCLASS. CLASS lcl_switch IMPLEMENTATION. METHOD constructor. mo_on_command = io_on. mo_off_command = io_off. ENDMETHOD. METHOD flip_up. mo_on_command->execute( ). ENDMETHOD. METHOD flip_down. mo_off_command->execute( ). ENDMETHOD. ENDCLASS. |
Теперь наш переключатель не содержит ссылок на конкретные устройства, вместо этого ему на вход поступают две команды на включение и отключение, которые что-то выполняют. При создании нового прибора нам будет достаточно определить новые команды включения и отключения и снабдить ими наш переключатель.
Бывают случаи, когда выключатели работают с целым набором устройств, для того чтобы реализовать нечто подобное, создадим следующий класс:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
CLASS lcl_command_list DEFINITION. PRIVATE SECTION. DATA: mt_command_list TYPE STANDARD TABLE OF lif_command. PUBLIC SECTION. METHODS: add_command IMPORTING io_command TYPE REF TO lif_command. ENDCLASS. CLASS lcl_command_list IMPLEMENTATION. METHOD add_command. APPEND io_command TO mt_command_list. ENDMETHOD. METHOD lif_command~execute. DATA: lo_command TYPE REF TO lif_command. LOOP AT mt_command_list INTO lo_command. CHECK lo_command IS BOUND. lo_command->execute( ). ENDLOOP. ENDMETHOD. ENDCLASS. |
Вместо обычных команд в переключатель мы теперь можем подавать составную команду, которая внутри себя содержит перечень других (для включения/отключения группы устройств), при этом наш класс переключатель не почувствует никакой разницы.
CLASS lcl_command_list DEFINITION.
PRIVATE SECTION.
DATA:
mt_command_list TYPE STANDARD TABLE OF lif_command.
Должно быть mt_command_list TYPE STANDARD TABLE OF !!REF TO!! lif_command.
В остальном, спасибо)