Decorator
Decorator — структурний патерн: обгортка навколо обʼєкта, що реалізує той самий інтерфейс і додає поведінку до/після виклику оригіналу. Дозволяє комбінувати поведінки через вкладені декоратори — щось на зразок шарів цибулини.
Метафора
Section titled “Метафора”Подарунок у кількох шарах упаковки. Основний подарунок — всередині. На ньому шар паперу, навколо — коробка, зверху — стрічка. Кожен шар додає щось (гарний вигляд, захист, таємничість), але подарунок залишається собою. Можна обгорнути в будь-якій комбінації шарів.
Коли застосовувати
Section titled “Коли застосовувати”- Треба динамічно додавати «вторинні» дії: логування, кешування, retry, метрики, авторизація, timeout.
- Ці дії — ортогональні бізнес-логіці і можуть комбінуватися у різних поєднаннях.
- Не хочеш роздувати базовий клас прапорцями «логувати/кешувати/retry».
- Наслідування тут не підходить — було б
N^2класів для всіх комбінацій.
ABAP-реалізація
Section titled “ABAP-реалізація”" Контракт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.
" Конкретний декоратор — додає timestampCLASS 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 vs Inheritance
Section titled “Decorator vs Inheritance”| Decorator | Наслідування | |
|---|---|---|
| Комбінація поведінок | ✅ N декораторів = 2^N комбінацій | ❌ Один клас — одна комбінація |
| Runtime-зміна | ✅ — міняй обгортку | ❌ — класова, статична |
| Кількість класів | 1 базовий + N декораторів | 2^N класів для всіх комбінацій |
| Коли переважає | Ортогональні фічі | Справжня «is-a» ієрархія |
SAP-специфіка
Section titled “SAP-специфіка”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— стандарт для таких задач.
Proxy vs Decorator
Section titled “Proxy vs Decorator”Структурно ідентичні (obj тримає inner, реалізують той же інтерфейс), різниця у намірі:
- Decorator — додає функціональність (лог, кеш, timestamp). Клієнт явно обирає.
- Proxy — керує доступом до обʼєкта (lazy init, authorization, remote call). Клієнт часто не знає, що перед ним proxy.
Підводні камені
Section titled “Підводні камені”- Однаковість інтерфейсу. 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.(але не зловживай прапорцями). - Інтерфейс нестабільний — кожна зміна зламає всі декоратори.