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

State

State — поведінковий патерн: поведінка обʼєкта змінюється разом з його станом. Кожен стан — окремий клас, що знає, як себе поводити і в який стан перейти після кожної дії. Замість довгого CASE mv_status WHEN 'NEW' ... WHEN 'PAID' ... WHEN 'SHIPPED' ... — поліморфний виклик на state-обʼєкт.

Вода: лід → вода → пар. Одна й та сама дія «нагріти» у кожному стані робить різне: лід перетворюється на воду, вода — на пар, пар при нагріві далі просто зростає у температурі. Обʼєкт-вода не знає, як реагувати на «нагріти» — це делегується обʼєкту-стану.

  • Обʼєкт має статуси з різною поведінкою — класичні приклади: Замовлення (new/paid/shipped/cancelled), Документ SAP (чернетка/розблокований/заблокований/архівний), Транспортний запит.
  • CASE mv_status з 5+ гілок дублюється в кількох методах (pay, ship, cancel — кожен має свою копію CASE).
  • Правила переходів складні: з NEW можна в PAID, але не в SHIPPED. Бізнес-логіка хоче заборонити невалідні переходи.
" Forward declaration — стан буде посилатись на order
CLASS zcl_order DEFINITION DEFERRED.
" Контракт стану
INTERFACE zif_order_state.
METHODS:
pay IMPORTING io_order TYPE REF TO zcl_order RAISING zcx_invalid_transition,
ship IMPORTING io_order TYPE REF TO zcl_order RAISING zcx_invalid_transition,
cancel IMPORTING io_order TYPE REF TO zcl_order RAISING zcx_invalid_transition.
ENDINTERFACE.
" Контекст — обʼєкт, що тримає поточний стан
CLASS zcl_order DEFINITION PUBLIC FINAL CREATE PUBLIC
GLOBAL FRIENDS zcl_order_state_new
zcl_order_state_paid
zcl_order_state_shipped.
PUBLIC SECTION.
METHODS:
constructor,
pay,
ship,
cancel.
PRIVATE SECTION.
DATA mo_state TYPE REF TO zif_order_state.
" дозволяємо станам міняти стан контексту
METHODS set_state IMPORTING io_state TYPE REF TO zif_order_state.
ENDCLASS.
CLASS zcl_order IMPLEMENTATION.
METHOD constructor.
mo_state = NEW zcl_order_state_new( ).
ENDMETHOD.
METHOD pay. mo_state->pay( me ). ENDMETHOD.
METHOD ship. mo_state->ship( me ). ENDMETHOD.
METHOD cancel. mo_state->cancel( me ). ENDMETHOD.
METHOD set_state. mo_state = io_state. ENDMETHOD.
ENDCLASS.

Конкретні стани:

CLASS zcl_order_state_new DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES zif_order_state.
ENDCLASS.
CLASS zcl_order_state_new IMPLEMENTATION.
METHOD zif_order_state~pay.
" бізнес-логіка оплати ...
io_order->set_state( NEW zcl_order_state_paid( ) ).
ENDMETHOD.
METHOD zif_order_state~ship.
RAISE EXCEPTION NEW zcx_invalid_transition( iv_from = 'NEW' iv_action = 'SHIP' ).
ENDMETHOD.
METHOD zif_order_state~cancel.
io_order->set_state( NEW zcl_order_state_cancelled( ) ).
ENDMETHOD.
ENDCLASS.
CLASS zcl_order_state_paid DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES zif_order_state.
ENDCLASS.
CLASS zcl_order_state_paid IMPLEMENTATION.
METHOD zif_order_state~pay.
RAISE EXCEPTION NEW zcx_invalid_transition( iv_from = 'PAID' iv_action = 'PAY' ).
ENDMETHOD.
METHOD zif_order_state~ship.
io_order->set_state( NEW zcl_order_state_shipped( ) ).
ENDMETHOD.
METHOD zif_order_state~cancel.
" можливо, з поверненням оплати
io_order->set_state( NEW zcl_order_state_cancelled( ) ).
ENDMETHOD.
ENDCLASS.

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

DATA(lo_order) = NEW zcl_order( ).
lo_order->pay( ). " NEW → PAID
lo_order->ship( ). " PAID → SHIPPED
TRY.
lo_order->pay( ). " SHIPPED → ? — виняток
CATCH zcx_invalid_transition.
ENDTRY.

Переваги:

  • кожен стан — окремий клас, легко читати і тестувати;
  • переходи задекларовані явно (set_state( NEW ... ));
  • невалідні переходи кидають виняток, а не тихо нічого не роблять.
StateStrategy
Обирає варіантОбʼєкт сам (всередині state-класу)Клієнт ззовні
Переходи між варіантамиЄ — state міняє stateНема — один вибір на час життя
НамірВідстежувати життєвий циклОбрати алгоритм
Обізнаність варіантівСтан знає сусідів (переходи)Стратегії незалежні

Код майже ідентичний — різниця в смислі.

Якщо стани не тримають власних даних, не потрібно створювати новий NEW на кожен перехід — зроби їх singleton-ами:

CLASS zcl_order_state_new DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS get_instance RETURNING VALUE(ro) TYPE REF TO zcl_order_state_new.
INTERFACES zif_order_state.
PRIVATE SECTION.
CLASS-DATA go_instance TYPE REF TO zcl_order_state_new.
ENDCLASS.

Зекономиш на алокаціях при масовій роботі.

  • Status Management (BS_02, Object status CRM_ORDER_STATUS_I1001) — SAP-нативний механізм станів, часто достатній без власного State-патерну.
  • RAP — Draft / Active States — RAP-фреймворк реалізує State під капотом; розробник описує дозволені переходи у behavior-definition.
  • Обʼєкт-state і обʼєкт-контекст тримають один одного. Є ризик циклічного посилання. У ABAP GC це обробить, але уважно — якщо десь додасться static reference, отримаєш витік.
  • Стан знає інші стани. set_state( NEW zcl_state_paid( ) ) — жорстка залежність. Для сильно динамічних переходів — зроби реєстр станів або фабрику.
  • Переходи не атомарні. Якщо pay( ) робить БД-запис і потім set_state( ) — між ними може впасти exception, і обʼєкт у памʼяті не відповідатиме даним у БД. Обгортай бізнес-операції у transaction-scope.
  • Серіалізація стану. Якщо обʼєкт зберігається у БД — у таблиці лежить код стану ('PAID'), а не обʼєкт. При читанні обʼєкта — треба відновити правильний state-клас. Це ще одна фабрика.

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

Section titled “Коли НЕ використовувати”
  • Статусів 2-3, без складної логіки переходів — bool-прапорець або проста enum-константа.
  • Поведінка не залежить від статусу, лише поле фіксує його значення — State не потрібен.
  • Обʼєкт — анемічний DTO, всю логіку робить сервіс. Стан зберігається на сервісі, не на DTO.