State
State — поведінковий патерн: поведінка обʼєкта змінюється разом з його станом. Кожен стан — окремий клас, що знає, як себе поводити і в який стан перейти після кожної дії. Замість довгого CASE mv_status WHEN 'NEW' ... WHEN 'PAID' ... WHEN 'SHIPPED' ... — поліморфний виклик на state-обʼєкт.
Метафора
Section titled “Метафора”Вода: лід → вода → пар. Одна й та сама дія «нагріти» у кожному стані робить різне: лід перетворюється на воду, вода — на пар, пар при нагріві далі просто зростає у температурі. Обʼєкт-вода не знає, як реагувати на «нагріти» — це делегується обʼєкту-стану.
Коли застосовувати
Section titled “Коли застосовувати”- Обʼєкт має статуси з різною поведінкою — класичні приклади: Замовлення (new/paid/shipped/cancelled), Документ SAP (чернетка/розблокований/заблокований/архівний), Транспортний запит.
CASE mv_statusз 5+ гілок дублюється в кількох методах (pay,ship,cancel— кожен має свою копію CASE).- Правила переходів складні: з
NEWможна вPAID, але не вSHIPPED. Бізнес-логіка хоче заборонити невалідні переходи.
ABAP-реалізація
Section titled “ABAP-реалізація”" Forward declaration — стан буде посилатись на orderCLASS 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 → PAIDlo_order->ship( ). " PAID → SHIPPEDTRY. lo_order->pay( ). " SHIPPED → ? — винятокCATCH zcx_invalid_transition.ENDTRY.Переваги:
- кожен стан — окремий клас, легко читати і тестувати;
- переходи задекларовані явно (
set_state( NEW ... )); - невалідні переходи кидають виняток, а не тихо нічого не роблять.
State vs Strategy
Section titled “State vs Strategy”| State | Strategy | |
|---|---|---|
| Обирає варіант | Обʼєкт сам (всередині state-класу) | Клієнт ззовні |
| Переходи між варіантами | Є — state міняє state | Нема — один вибір на час життя |
| Намір | Відстежувати життєвий цикл | Обрати алгоритм |
| Обізнаність варіантів | Стан знає сусідів (переходи) | Стратегії незалежні |
Код майже ідентичний — різниця в смислі.
Stateless states через singleton
Section titled “Stateless states через singleton”Якщо стани не тримають власних даних, не потрібно створювати новий 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.Зекономиш на алокаціях при масовій роботі.
SAP-специфіка
Section titled “SAP-специфіка”- Status Management (
BS_02, Object statusCRM_ORDER_STATUS_I1001) — SAP-нативний механізм станів, часто достатній без власного State-патерну. - RAP — Draft / Active States — RAP-фреймворк реалізує State під капотом; розробник описує дозволені переходи у behavior-definition.
Підводні камені
Section titled “Підводні камені”- Обʼєкт-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.