Builder
Builder — породжуючий патерн для покрокової побудови складного обʼєкта. Замість конструктора з двадцятьма параметрами — ланцюг методів, кожен з яких задає одну властивість і повертає білдер назад. На фінал — build( ) або receive( ) виконує валідацію і віддає готовий обʼєкт.
Метафора
Section titled “Метафора”Замовлення кави в кафе: «середня, з вівсяним молоком, два шоти, без цукру, з собою». Бариста будує напій покроково — кожен інгредієнт додається окремо. Те саме замовлення можна було б зробити одним рядком, але тоді порядок параметрів і пропуски стали б кошмаром.
Коли застосовувати
Section titled “Коли застосовувати”- Конструктор уже має 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.SAP-специфіка
Section titled “SAP-специфіка”cl_abap_structdescr=>create( )+ методи додавання компонентів — builder для динамічних структур.- RAP EML —
MODIFY ENTITY ... CREATE FROM ... CREATE BY \_...— по суті builder-синтаксис на рівні мови. COND/SWITCH+VALUE #( FOR ... IN ... WHERE )— не builder-и, а конструкторські вирази, але часто вирішують ту саму задачу елегантніше.
Підводні камені
Section titled “Підводні камені”- Забутий
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. Використовуй, коли продукт будується в багатьох місцях з різними комбінаціями.