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

Composite

Composite — структурний патерн, що дозволяє однаково звертатись до окремого обʼєкта і до контейнера обʼєктів. Ієрархія будується як дерево: лист (leaf) і композит (composite) реалізують один інтерфейс. Клієнт викликає метод — не знаючи, чи перед ним окремий елемент чи ціла гілка.

Файлова система: файл і папка. «Розмір» можна запитати у обох — у файла це просто його розмір, у папки — сума розмірів усіх вкладених (включно з підпапками, рекурсивно). Команда du не розрізняє файл/папку — вона обходить дерево через однаковий інтерфейс.

  • Дані природньо мають деревоподібну структуру: організаційна ієрархія, BOM (Bill of Materials), файлова система, UI-елементи з групами.
  • Над деревом треба робити операції однаково для листа і гілки (сума, обхід, render).
  • Клієнт не повинен перейматись, який рівень дерева перед ним.

Приклад — дерево 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.

Рахунок ваги повної зборки — рекурсивно через листя і підзборки.

  • 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.
  • 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 по дочірньому масиву простіший і зрозуміліший.