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

Strategy

Strategy — поведінковий патерн, що дозволяє змінювати алгоритм у runtime без правки клієнта. Варіанти алгоритму — окремі класи, які реалізують спільний інтерфейс. Клієнт тримає посилання на інтерфейс і викликає його, не знаючи про конкретну реалізацію.

Калькулятор знижки у інтернет-магазині: є знижка для постійних клієнтів, сезонна, за промокодом, немає знижки взагалі. Логіка в кожному випадку різна, але вхід і вихід однакові: сума → нова сума. Кошик покупок не повинен знати, яка саме знижка застосовується — він просить стратегію порахувати.

  • Є кілька варіантів обчислення одного й того самого: розрахунок знижки, алгоритм сортування, спосіб форматування.
  • IF/CASE-розгалуження за типом стає великим і тягнеться у кілька методів.
  • Варіант потрібно обирати у runtime, за користувачем, customizing-ом, або BAdI.
  • Треба додавати нові варіанти без правки існуючого коду (OCP).
" Контракт стратегії
INTERFACE zif_discount.
METHODS calc
IMPORTING iv_amount TYPE p
iv_customer_id TYPE kunnr
RETURNING VALUE(rv_final) TYPE p.
ENDINTERFACE.
" Конкретні стратегії
CLASS zcl_discount_none DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES zif_discount.
ENDCLASS.
CLASS zcl_discount_none IMPLEMENTATION.
METHOD zif_discount~calc. rv_final = iv_amount. ENDMETHOD.
ENDCLASS.
CLASS zcl_discount_vip DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES zif_discount.
ENDCLASS.
CLASS zcl_discount_vip IMPLEMENTATION.
METHOD zif_discount~calc. rv_final = iv_amount * '0.85'. ENDMETHOD.
ENDCLASS.
CLASS zcl_discount_promo DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES zif_discount.
METHODS constructor IMPORTING iv_code TYPE string.
PRIVATE SECTION.
DATA mv_code TYPE string.
ENDCLASS.
" Клієнт — знає тільки про інтерфейс
CLASS zcl_cart DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
constructor IMPORTING io_discount TYPE REF TO zif_discount,
total RETURNING VALUE(rv_sum) TYPE p.
PRIVATE SECTION.
DATA:
mo_discount TYPE REF TO zif_discount,
mt_items TYPE TABLE OF zty_item.
ENDCLASS.
CLASS zcl_cart IMPLEMENTATION.
METHOD constructor. mo_discount = io_discount. ENDMETHOD.
METHOD total.
DATA(lv_raw) = REDUCE p( INIT s = 0
FOR ls IN mt_items
NEXT s = s + ls-price * ls-qty ).
rv_sum = mo_discount->calc( iv_amount = lv_raw
iv_customer_id = mv_customer_id ).
ENDMETHOD.
ENDCLASS.

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

DATA lo_discount TYPE REF TO zif_discount.
CASE lv_customer_class.
WHEN 'VIP'. lo_discount = NEW zcl_discount_vip( ).
WHEN 'PROMO'. lo_discount = NEW zcl_discount_promo( iv_code = lv_code ).
WHEN OTHERS. lo_discount = NEW zcl_discount_none( ).
ENDCASE.
DATA(lo_cart) = NEW zcl_cart( io_discount = lo_discount ).

Зміна правил знижки — додаєш новий клас (наприклад, zcl_discount_seasonal) і передаєш його у zcl_cart. Код zcl_cart не змінюється.

  • Strategy — алгоритм всередині одного методу, свобода вибору до виклику.
  • State — поведінка залежить від стану обʼєкта, перемикається сам обʼєкт.
  • Template Method — скелет фіксований, змінні кроки переозначаються у підкласі (статичний вибір).

Strategy та State виглядають майже однаково в коді. Різниця — у наміру: хто обирає реалізацію. Strategy — клієнт знає ззовні. State — обʼєкт перемикає себе зсередини.

  • BAdI (Business Add-In) — SAP-стандартний механізм strategy: стандартний код викликає BAdI-інтерфейс, замовник підсуває свою реалізацію через CL_BADI_MANAGER.
  • BTE, BRF+ правила — стратегії, які обираються за customizing-ключем.
  • RAP behavior implementation — кожна determination/validation — потенційний strategy-інтерфейс.
  • Синдром Single-Strategy. Якщо стратегія завжди одна — не роби інтерфейс. Переробиш, коли знадобиться друга.
  • Параметри у стратегій різні. Якщо calc у різних стратегіях хоче різні вхідні дані — пакуй у TY_CONTEXT-структуру, або передавай клієнт-обʼєкт як io_cart.
  • Вибір стратегії розповзся. Якщо CASE lv_type WHEN ... дублюється в 5 місцях — витягуй у фабрику стратегій.
  • Залежність від глобального стану. Стратегія не повинна лізти у sy-* чи singleton-и — інакше тест неможливий.

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

Section titled “Коли НЕ використовувати”
  • Одна реалізація — прямий виклик коротший і зрозуміліший.
  • Алгоритм — IF/ELSE з двох рядків. Не роби клас на кожну мінливу примху.
  • Швидкість критична у тугому циклі — поліморфний виклик у ABAP дешевий, але не безплатний.