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

Builder

Builder — породжуючий патерн для покрокової побудови складного обʼєкта. Замість конструктора з двадцятьма параметрами — ланцюг методів, кожен з яких задає одну властивість і повертає білдер назад. На фінал — build( ) або receive( ) виконує валідацію і віддає готовий обʼєкт.

Замовлення кави в кафе: «середня, з вівсяним молоком, два шоти, без цукру, з собою». Бариста будує напій покроково — кожен інгредієнт додається окремо. Те саме замовлення можна було б зробити одним рядком, але тоді порядок параметрів і пропуски стали б кошмаром.

  • Конструктор уже має 5+ параметрів, і більшість із них опційні.
  • Порядок зборки має значення (спочатку from, потім where, потім order by).
  • Один процес збирання має давати різні варіанти результату (той самий білдер — SQL для HANA чи Oracle).
  • Хочеш читабельний клієнтський код: builder->set_x( )->set_y( )->build( ) замість NEW obj( x, y, z, iv_has_foo, iv_has_bar, ... ).

ABAP-реалізація — fluent-білдер

Section titled “ABAP-реалізація — fluent-білдер”

Канонічна форма: методи повертають me, а build( ) віддає готовий обʼєкт.

CLASS zcl_query_builder DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
from IMPORTING iv_table TYPE string
RETURNING VALUE(ro) TYPE REF TO zcl_query_builder,
where IMPORTING iv_cond TYPE string
RETURNING VALUE(ro) TYPE REF TO zcl_query_builder,
order_by IMPORTING iv_cols TYPE string
RETURNING VALUE(ro) TYPE REF TO zcl_query_builder,
build RETURNING VALUE(rv_sql) TYPE string
RAISING zcx_invalid_query.
PRIVATE SECTION.
DATA:
mv_table TYPE string,
mt_where TYPE STANDARD TABLE OF string WITH EMPTY KEY,
mv_order TYPE string.
ENDCLASS.
CLASS zcl_query_builder IMPLEMENTATION.
METHOD from. mv_table = iv_table. ro = me. ENDMETHOD.
METHOD where. APPEND iv_cond TO mt_where. ro = me. ENDMETHOD.
METHOD order_by. mv_order = iv_cols. ro = me. ENDMETHOD.
METHOD build.
IF mv_table IS INITIAL.
RAISE EXCEPTION NEW zcx_invalid_query( iv_reason = 'FROM missing' ).
ENDIF.
rv_sql = |SELECT * FROM { mv_table }|.
IF mt_where IS NOT INITIAL.
rv_sql &&= | WHERE { concat_lines_of( table = mt_where sep = ` AND ` ) }|.
ENDIF.
IF mv_order IS NOT INITIAL.
rv_sql &&= | ORDER BY { mv_order }|.
ENDIF.
ENDMETHOD.
ENDCLASS.

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

DATA(lv_sql) = NEW zcl_query_builder( )->from( 'MARA'
)->where( `matnr LIKE 'A%'`
)->where( `mtart = 'FERT'`
)->order_by( 'matnr'
)->build( ).

ABAP-реалізація — окремий білдер

Section titled “ABAP-реалізація — окремий білдер”

Коли продукт — окремий клас, а білдер — допоміжний, який його будує:

CLASS zcl_report_builder DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
set_title IMPORTING iv_title TYPE string
RETURNING VALUE(ro) TYPE REF TO zcl_report_builder,
set_header IMPORTING iv_header TYPE string
RETURNING VALUE(ro) TYPE REF TO zcl_report_builder,
add_section IMPORTING io_section TYPE REF TO zif_section
RETURNING VALUE(ro) TYPE REF TO zcl_report_builder,
build RETURNING VALUE(ro) TYPE REF TO zcl_report.
PRIVATE SECTION.
DATA mo_product TYPE REF TO zcl_report.
ENDCLASS.

mo_product створюється лінькою — у першому set_*-виклику, або у конструкторі білдера. На build( ) валідується повнота і повертається.

Director — коли варіацій небагато

Section titled “Director — коли варіацій небагато”

Коли типових рецептів збірки кілька (наприклад, «стандартний звіт», «стислий звіт», «детальний звіт») — додай Director, що інкапсулює рецепт:

CLASS zcl_report_director DEFINITION PUBLIC FINAL.
PUBLIC SECTION.
CLASS-METHODS build_standard
IMPORTING io_builder TYPE REF TO zcl_report_builder
RETURNING VALUE(ro) TYPE REF TO zcl_report.
ENDCLASS.
  • cl_abap_structdescr=>create( ) + методи додавання компонентів — builder для динамічних структур.
  • RAP EML — MODIFY ENTITY ... CREATE FROM ... CREATE BY \_... — по суті builder-синтаксис на рівні мови.
  • COND / SWITCH + VALUE #( FOR ... IN ... WHERE ) — не builder-и, а конструкторські вирази, але часто вирішують ту саму задачу елегантніше.
  • Забутий ro = me. Класична помилка: метод не повертає me — chain рветься мовчки (наступний виклик йде на NULL і падає короткодампом). Переконайся, що кожен setter повертає me.
  • Продукт створюється у білдері, але віддається по REF. Якщо білдер перевикористовується — build( ) має повертати новий обʼєкт або копію, інакше два build( ) віддадуть посилання на той самий продукт.
  • Валідація — в build( ), не у кожному setter-і. Інакше порядок викликів зафіксується («спочатку from, потім where, інакше виняток»).
  • Immutable product. Після build( ) обʼєкт краще мати read-only — інакше зміни у ньому «назад» ламають інваріанти. Це дисципліна, не завжди enforced.
  • Не плутай з Fluent Interface. Fluent Interface — техніка (методи повертають me). Builder — патерн (покрокова побудова + build( )). Builder часто реалізується як fluent, але не навпаки.

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

Section titled “Коли НЕ використовувати”
  • Обʼєкт простий, 2-3 обовʼязкові параметри — достатньо NEW.
  • Усі параметри обовʼязкові і в чіткому порядку — NEW читабельніший.
  • Для разового створення — overkill. Використовуй, коли продукт будується в багатьох місцях з різними комбінаціями.