Перейти до вмісту

Command

Command — поведінковий патерн: запит на дію стає обʼєктом. Обʼєкт тримає посилання на одержувача (receiver) та параметри виклику, і має єдиний метод execute( ). Це відкриває можливості: ставити команди в чергу, логувати, повторювати, відкочувати (undo), транспортувати через мережу.

Кнопка вмикача світла. Сама кнопка не знає, що саме увімкне — лампу, вентилятор чи двигун. Вона просто «натискає» — і десь вдалині виконується дія. Заміна пристрою — не потребує зміни кнопки.

  • UI-меню/тулбар, де кнопка не повинна знати бізнес-логіку — вона просто тригерить команду.
  • Треба undo/redo — зберігаєш стек виконаних команд з можливістю відкату.
  • Команди потрібно серіалізувати (журнал транзакцій, queue для асинхронного виконання).
  • Треба лог виконаних операцій або повтор одної дії на різних обʼєктах.
  • Макроси: послідовність команд як одна композитна команда.
" Контракт команди
INTERFACE zif_command.
METHODS:
execute,
undo.
ENDINTERFACE.
" Receiver — справжній виконавець
CLASS zcl_order DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
add_item IMPORTING iv_matnr TYPE matnr iv_qty TYPE p,
remove_item IMPORTING iv_matnr TYPE matnr iv_qty TYPE p.
ENDCLASS.
" Конкретна команда — тримає receiver і параметри
CLASS zcl_cmd_add_item DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES zif_command.
METHODS constructor
IMPORTING io_order TYPE REF TO zcl_order
iv_matnr TYPE matnr
iv_qty TYPE p.
PRIVATE SECTION.
DATA:
mo_order TYPE REF TO zcl_order,
mv_matnr TYPE matnr,
mv_qty TYPE p.
ENDCLASS.
CLASS zcl_cmd_add_item IMPLEMENTATION.
METHOD constructor.
mo_order = io_order.
mv_matnr = iv_matnr.
mv_qty = iv_qty.
ENDMETHOD.
METHOD zif_command~execute.
mo_order->add_item( iv_matnr = mv_matnr iv_qty = mv_qty ).
ENDMETHOD.
METHOD zif_command~undo.
mo_order->remove_item( iv_matnr = mv_matnr iv_qty = mv_qty ).
ENDMETHOD.
ENDCLASS.
" Invoker — запускає, не знає конкретної команди
CLASS zcl_cmd_invoker DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
execute IMPORTING io_command TYPE REF TO zif_command,
undo_last.
PRIVATE SECTION.
DATA mt_history TYPE STANDARD TABLE OF REF TO zif_command WITH EMPTY KEY.
ENDCLASS.
CLASS zcl_cmd_invoker IMPLEMENTATION.
METHOD execute.
io_command->execute( ).
APPEND io_command TO mt_history.
ENDMETHOD.
METHOD undo_last.
DATA(lv_idx) = lines( mt_history ).
CHECK lv_idx > 0.
READ TABLE mt_history INDEX lv_idx INTO DATA(lo_last).
lo_last->undo( ).
DELETE mt_history INDEX lv_idx.
ENDMETHOD.
ENDCLASS.

Використання:

DATA(lo_order) = NEW zcl_order( ).
DATA(lo_invoker) = NEW zcl_cmd_invoker( ).
lo_invoker->execute( NEW zcl_cmd_add_item(
io_order = lo_order iv_matnr = 'A1' iv_qty = 5 ) ).
lo_invoker->execute( NEW zcl_cmd_add_item(
io_order = lo_order iv_matnr = 'A2' iv_qty = 3 ) ).
lo_invoker->undo_last( ). " відкочує додавання A2

Команда, що містить інші команди:

CLASS zcl_cmd_macro DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES zif_command.
METHODS add IMPORTING io_command TYPE REF TO zif_command.
PRIVATE SECTION.
DATA mt_commands TYPE STANDARD TABLE OF REF TO zif_command WITH EMPTY KEY.
ENDCLASS.
CLASS zcl_cmd_macro IMPLEMENTATION.
METHOD zif_command~execute.
LOOP AT mt_commands INTO DATA(lo). lo->execute( ). ENDLOOP.
ENDMETHOD.
METHOD zif_command~undo.
" undo — у зворотному порядку
LOOP AT mt_commands INTO DATA(lo_cmd). ENDLOOP.
DO lines( mt_commands ) TIMES.
DATA(lv_idx) = lines( mt_commands ) - sy-index + 1.
READ TABLE mt_commands INDEX lv_idx INTO lo_cmd.
lo_cmd->undo( ).
ENDDO.
ENDMETHOD.
ENDCLASS.
  • sy-ucomm диспетчер у класичних диналогах — наївна форма Command: CASE sy-ucomm WHEN 'SAVE'. WHEN 'DEL'. Але без обʼєктів — не можна undo, не можна логувати.
  • Update Tasks (CALL FUNCTION ... IN UPDATE TASK) — рід асинхронних Command-ів на рівні ядра.
  • Workflow — event + step = фактично command у runtime-оркестрації.
  • Undo не завжди можливий. Якщо команда мала побічний ефект (RFC-виклик, email, друк) — відкат — це інша команда, не просто зворотна дія. Іноді undo взагалі неможливий.
  • Стан у команді. Команда тримає параметри і receiver. Якщо receiver довгоживучий, а команда потрібна тільки для одного виконання — не тримай історію довше, ніж треба. Витоки.
  • Серіалізація. Якщо треба зберігати команди у БД (для later replay або audit) — команда не може містити обʼєкти без контракту серіалізації. Пакуй параметри у DTO-структури.
  • Перетягування логіки. Команда має координувати дію на receiver-і, а не містити всю бізнес-логіку. Інакше receiver стає анемічним, а команда — богом.

Коли НЕ використовувати

Section titled “Коли НЕ використовувати”
  • Просто виклик методу — не роби навколо нього команду-обʼєкт.
  • Undo не потрібен, логування не потрібне, черги нема. Без цих юз-кейсів Command — overkill.
  • Перформанс-критичний код у тісному циклі — створення обʼєкта на кожну дію додає витрат.