Composite
Composite — структурний патерн, що дозволяє однаково звертатись до окремого обʼєкта і до контейнера обʼєктів. Ієрархія будується як дерево: лист (leaf) і композит (composite) реалізують один інтерфейс. Клієнт викликає метод — не знаючи, чи перед ним окремий елемент чи ціла гілка.
Метафора
Section titled “Метафора”Файлова система: файл і папка. «Розмір» можна запитати у обох — у файла це просто його розмір, у папки — сума розмірів усіх вкладених (включно з підпапками, рекурсивно). Команда du не розрізняє файл/папку — вона обходить дерево через однаковий інтерфейс.
Коли застосовувати
Section titled “Коли застосовувати”- Дані природньо мають деревоподібну структуру: організаційна ієрархія, BOM (Bill of Materials), файлова система, UI-елементи з групами.
- Над деревом треба робити операції однаково для листа і гілки (сума, обхід, render).
- Клієнт не повинен перейматись, який рівень дерева перед ним.
ABAP-реалізація
Section titled “ABAP-реалізація”Приклад — дерево UI-елементів (render):
INTERFACE zif_ui_component. METHODS render RETURNING VALUE(rv_html) TYPE string.ENDINTERFACE.
" Leaf — окремий елементCLASS zcl_ui_button DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES zif_ui_component. METHODS constructor IMPORTING iv_label TYPE string. PRIVATE SECTION. DATA mv_label TYPE string.ENDCLASS.
CLASS zcl_ui_button IMPLEMENTATION. METHOD constructor. mv_label = iv_label. ENDMETHOD. METHOD zif_ui_component~render. rv_html = |<button>{ mv_label }</button>|. ENDMETHOD.ENDCLASS.
" Composite — контейнерCLASS zcl_ui_panel DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES zif_ui_component. METHODS: constructor IMPORTING iv_id TYPE string, add IMPORTING io_child TYPE REF TO zif_ui_component RETURNING VALUE(ro) TYPE REF TO zcl_ui_panel. PRIVATE SECTION. DATA: mv_id TYPE string, mt_children TYPE STANDARD TABLE OF REF TO zif_ui_component WITH EMPTY KEY.ENDCLASS.
CLASS zcl_ui_panel IMPLEMENTATION. METHOD constructor. mv_id = iv_id. ENDMETHOD.
METHOD add. APPEND io_child TO mt_children. ro = me. ENDMETHOD.
METHOD zif_ui_component~render. DATA(lv_inner) = REDUCE string( INIT s = `` FOR lo IN mt_children NEXT s = s && lo->render( ) ). rv_html = |<div id="{ mv_id }">{ lv_inner }</div>|. ENDMETHOD.ENDCLASS.Використання:
DATA(lo_panel) = NEW zcl_ui_panel( iv_id = 'main' ).
lo_panel->add( NEW zcl_ui_button( 'OK' ) )->add( NEW zcl_ui_button( 'Cancel' ) ).
DATA(lo_sub) = NEW zcl_ui_panel( iv_id = 'sub' ).lo_sub->add( NEW zcl_ui_button( 'Help' ) ).lo_panel->add( lo_sub ).
WRITE lo_panel->zif_ui_component~render( )." <div id="main"><button>OK</button><button>Cancel</button>" <div id="sub"><button>Help</button></div></div>Одна і та сама операція render( ) викликається для кнопок і для панелей — клієнт не розрізняє.
Приклад SAP — BOM (Bill of Materials)
Section titled “Приклад SAP — BOM (Bill of Materials)”INTERFACE zif_bom_item. METHODS get_total_weight RETURNING VALUE(rv_kg) TYPE p.ENDINTERFACE.
" Leaf — окремий матеріалCLASS zcl_bom_material DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES zif_bom_item. METHODS constructor IMPORTING iv_matnr TYPE matnr iv_weight TYPE p iv_qty TYPE p. PRIVATE SECTION. DATA: mv_matnr TYPE matnr, mv_weight TYPE p, mv_qty TYPE p.ENDCLASS.
CLASS zcl_bom_material IMPLEMENTATION. METHOD constructor. mv_matnr = iv_matnr. mv_weight = iv_weight. mv_qty = iv_qty. ENDMETHOD. METHOD zif_bom_item~get_total_weight. rv_kg = mv_weight * mv_qty. ENDMETHOD.ENDCLASS.
" Composite — зборкаCLASS zcl_bom_assembly DEFINITION PUBLIC FINAL CREATE PUBLIC. PUBLIC SECTION. INTERFACES zif_bom_item. METHODS add IMPORTING io_child TYPE REF TO zif_bom_item. PRIVATE SECTION. DATA mt_children TYPE STANDARD TABLE OF REF TO zif_bom_item WITH EMPTY KEY.ENDCLASS.
CLASS zcl_bom_assembly IMPLEMENTATION. METHOD add. APPEND io_child TO mt_children. ENDMETHOD. METHOD zif_bom_item~get_total_weight. rv_kg = REDUCE #( INIT s = 0 FOR lo IN mt_children NEXT s = s + lo->get_total_weight( ) ). ENDMETHOD.ENDCLASS.Рахунок ваги повної зборки — рекурсивно через листя і підзборки.
SAP-специфіка
Section titled “SAP-специфіка”- Hierarchical CDS views (
WITH HIERARCHY) — декларативний composite, SAP-сам обходить дерево. - ALV tree (
cl_gui_alv_tree) — рендер дерева з листя і вузлів, композитний підхід. - BOM (
cs_bom_expl_mat_v2) — SAP API для вивантаження дерева, композит на рівні даних. - Hierarchies у ABAP SQL — див. sql/hierarchies.
Підводні камені
Section titled “Підводні камені”- Transparent vs safe Composite. Transparent (інтерфейс має
add/removeдля всіх, і лист ігнорує) — клієнт уніфікований, але лист має безглуздий метод. Safe (add/remove тільки у composite, лист їх не має) — більш чисто, але клієнт має знати тип для керування дітьми. У прикладі вище використано safe. - Циклічні посилання. Якщо випадково дочірнім елементом назначити предка — нескінченна рекурсія. При додаванні перевіряй, що нащадок не є предком.
- Підрахунок на великих деревах.
REDUCEрекурсивний — для 100k-вузлів може бути повільно. Розглянь кешування сумарних значень у composite. - Спільний child у кількох parents. Якщо лист додано в два контейнери — зміна його стану відобразиться в обох. Вирішуй свідомо: клонуй (Prototype) або явно дозволяй sharing.
- Обхід через visitor. Якщо операцій над деревом багато — натягуй Visitor замість роздування інтерфейсу composite.
Коли НЕ використовувати
Section titled “Коли НЕ використовувати”- Структура — плоский список, без гнізд.
- Листя і «контейнер» поводяться принципово по-різному — не натягуй один інтерфейс силоміць.
- Глибина дерева — 2 рівні. Просто
LOOP ATпо дочірньому масиву простіший і зрозуміліший.
Див. також
Section titled “Див. також”- Ієрархії в ABAP SQL — як декларативні composite-запити.