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

Decorator

Decorator — структурний патерн: обгортка навколо обʼєкта, що реалізує той самий інтерфейс і додає поведінку до/після виклику оригіналу. Дозволяє комбінувати поведінки через вкладені декоратори — щось на зразок шарів цибулини.

Подарунок у кількох шарах упаковки. Основний подарунок — всередині. На ньому шар паперу, навколо — коробка, зверху — стрічка. Кожен шар додає щось (гарний вигляд, захист, таємничість), але подарунок залишається собою. Можна обгорнути в будь-якій комбінації шарів.

  • Треба динамічно додавати «вторинні» дії: логування, кешування, retry, метрики, авторизація, timeout.
  • Ці дії — ортогональні бізнес-логіці і можуть комбінуватися у різних поєднаннях.
  • Не хочеш роздувати базовий клас прапорцями «логувати/кешувати/retry».
  • Наслідування тут не підходить — було б N^2 класів для всіх комбінацій.
" Контракт
INTERFACE zif_writer.
METHODS write
IMPORTING iv_text TYPE string
RAISING zcx_write_error.
ENDINTERFACE.
" Основний компонент
CLASS zcl_file_writer DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES zif_writer.
METHODS constructor IMPORTING iv_path TYPE string.
PRIVATE SECTION.
DATA mv_path TYPE string.
ENDCLASS.
CLASS zcl_file_writer IMPLEMENTATION.
METHOD constructor. mv_path = iv_path. ENDMETHOD.
METHOD zif_writer~write.
" справжній запис у файл
ENDMETHOD.
ENDCLASS.
" Базовий декоратор — обгортка, що реалізує той самий інтерфейс
CLASS zcl_writer_decorator DEFINITION PUBLIC ABSTRACT.
PUBLIC SECTION.
INTERFACES zif_writer.
METHODS constructor IMPORTING io_inner TYPE REF TO zif_writer.
PROTECTED SECTION.
DATA mo_inner TYPE REF TO zif_writer.
ENDCLASS.
CLASS zcl_writer_decorator IMPLEMENTATION.
METHOD constructor. mo_inner = io_inner. ENDMETHOD.
METHOD zif_writer~write.
" дефолт: прозоре делегування
mo_inner->write( iv_text ).
ENDMETHOD.
ENDCLASS.
" Конкретний декоратор — додає timestamp
CLASS zcl_writer_timestamp DEFINITION PUBLIC FINAL
INHERITING FROM zcl_writer_decorator.
PUBLIC SECTION.
METHODS zif_writer~write REDEFINITION.
ENDCLASS.
CLASS zcl_writer_timestamp IMPLEMENTATION.
METHOD zif_writer~write.
mo_inner->write( |[{ sy-datum }-{ sy-uzeit }] { iv_text }| ).
ENDMETHOD.
ENDCLASS.
" Інший декоратор — нумерація
CLASS zcl_writer_line_nr DEFINITION PUBLIC FINAL
INHERITING FROM zcl_writer_decorator.
PUBLIC SECTION.
METHODS zif_writer~write REDEFINITION.
PRIVATE SECTION.
DATA mv_counter TYPE i.
ENDCLASS.
CLASS zcl_writer_line_nr IMPLEMENTATION.
METHOD zif_writer~write.
mv_counter = mv_counter + 1.
mo_inner->write( |{ mv_counter }: { iv_text }| ).
ENDMETHOD.
ENDCLASS.

Використання — комбінуємо декоратори:

DATA(lo_writer) = CAST zif_writer(
NEW zcl_writer_timestamp(
NEW zcl_writer_line_nr(
NEW zcl_file_writer( iv_path = '/tmp/log.txt' ) ) ) ).
lo_writer->write( 'Hello' ).
" file отримує: "[20260421-120000] 1: Hello"

Порядок шарів важить: timestamp( line_nr( file ) )line_nr( timestamp( file ) ).

DecoratorНаслідування
Комбінація поведінок✅ N декораторів = 2^N комбінацій❌ Один клас — одна комбінація
Runtime-зміна✅ — міняй обгортку❌ — класова, статична
Кількість класів1 базовий + N декораторів2^N класів для всіх комбінацій
Коли переважаєОртогональні фічіСправжня «is-a» ієрархія
  • cl_gui_alv_grid події обробляються через ланцюг handler-ів — концептуально схоже з decorator для user-command.
  • BAdI filter decoration — SAP-стандарт робить CL_BADI_MANAGER=>GET_ACTIVE_IMPLEMENTATIONS — фактично обгортає виклик з фільтром.
  • Enhancement Framework — pre/post methods — прямий decorator: код «до» і «після» методу без його модифікації.
  • У проекті — zcl_writer_cached, zcl_writer_retried, zcl_rfc_logged — стандарт для таких задач.

Структурно ідентичні (obj тримає inner, реалізують той же інтерфейс), різниця у намірі:

  • Decorator — додає функціональність (лог, кеш, timestamp). Клієнт явно обирає.
  • Proxy — керує доступом до обʼєкта (lazy init, authorization, remote call). Клієнт часто не знає, що перед ним proxy.
  • Однаковість інтерфейсу. Decorator має точно той самий інтерфейс, що і оригінал. Нові методи через decorator не додавай — клієнт їх не побачить, бо типізований під інтерфейс.
  • Порядок має значення. logger( retrier( inner ) ) логує кожну спробу; retrier( logger( inner ) ) логує тільки фінальний результат. Вирішуй свідомо.
  • Втрата ідентичності. lo_writer вже не те саме, що inner_writer. IS INSTANCE OF zcl_file_writer поверне FALSE. Якщо клієнт полагається на конкретний тип — decorator ламає це.
  • Тестування шарів. Тестуй кожен декоратор окремо з fake-inner-ом, тоді їх комбінації — простіше.
  • Глибина стеку. 10 вкладених декораторів — 10 frame-ів у стеку на кожен виклик. Не критично, але трейси читати важко.

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

Section titled “Коли НЕ використовувати”
  • Нова поведінка потрібна всім і завжди — додавай у базовий клас.
  • Тільки одна варіація — просто IF iv_log = abap_true. log(...). ENDIF. (але не зловживай прапорцями).
  • Інтерфейс нестабільний — кожна зміна зламає всі декоратори.