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

Factory Method

Factory Method — породжуючий патерн: створення конкретного класу інкапсульовано в окремому методі, а клієнт отримує обʼєкт через інтерфейс або абстрактний базовий клас. Вибір конкретного класу залежить від параметра.

Логістична компанія дає клієнту «транспорт». Чи це буде фура, вагон, чи баржа — вирішує диспетчер на основі маршруту і ваги. Клієнт про це не знає — він просто завантажує вантаж.

  • У клієнта має бути один контракт на сімейство реалізацій (парсери, експортери, калькулятори знижок).
  • Конкретний клас обирається на основі вхідних даних (формат файлу, тип обʼєкта, customizing-параметр).
  • Не хочеш розмазувати CASE по всьому коду — він збирається в одному місці у фабриці.

Рекомендована форма — окремий клас-фабрика з CLASS-METHOD create. Альтернатива — статичний метод у базовому класі, але це змішує дві відповідальності (поведінка + побудова).

INTERFACE zif_report_writer.
METHODS write
IMPORTING it_data TYPE REF TO data
RAISING zcx_writer_error.
ENDINTERFACE.
CLASS zcl_alv_writer DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION.
INTERFACES zif_report_writer.
ENDCLASS.
CLASS zcl_pdf_writer DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION.
INTERFACES zif_report_writer.
ENDCLASS.
CLASS zcl_xlsx_writer DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION.
INTERFACES zif_report_writer.
ENDCLASS.
CLASS zcl_writer_factory DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
TYPES ty_format TYPE c LENGTH 4.
CONSTANTS:
mc_alv TYPE ty_format VALUE 'ALV',
mc_pdf TYPE ty_format VALUE 'PDF',
mc_xlsx TYPE ty_format VALUE 'XLSX'.
CLASS-METHODS create
IMPORTING iv_format TYPE ty_format
RETURNING VALUE(ro_writer) TYPE REF TO zif_report_writer
RAISING zcx_unknown_format.
ENDCLASS.
CLASS zcl_writer_factory IMPLEMENTATION.
METHOD create.
CASE iv_format.
WHEN mc_alv. ro_writer = NEW zcl_alv_writer( ).
WHEN mc_pdf. ro_writer = NEW zcl_pdf_writer( ).
WHEN mc_xlsx. ro_writer = NEW zcl_xlsx_writer( ).
WHEN OTHERS.
RAISE EXCEPTION NEW zcx_unknown_format( iv_format = iv_format ).
ENDCASE.
ENDMETHOD.
ENDCLASS.

Конкретні класи зроблені CREATE PRIVATE + FRIENDS zcl_writer_factory — інстансувати їх може тільки фабрика. Це захищає контракт: клієнт не створить zcl_alv_writer( ) повз фабрику і не оминатиме валідацію формату.

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

DATA(lo_writer) = zcl_writer_factory=>create( zcl_writer_factory=>mc_pdf ).
lo_writer->write( REF #( mt_alv ) ).

Варіант із «поліморфним конструктором» у базовому класі

Section titled “Варіант із «поліморфним конструктором» у базовому класі”

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

CLASS lcl_base_writer DEFINITION ABSTRACT.
PUBLIC SECTION.
CLASS-METHODS get_writer
IMPORTING iv_format TYPE string
RETURNING VALUE(ro_writer) TYPE REF TO lcl_base_writer.
METHODS write ABSTRACT.
ENDCLASS.

Недолік: базовий клас знає про всіх своїх нащадків — порушує відкритість/закритість. Для production-коду — краще окрема фабрика.

  • cl_salv_table=>factory( ) — канонічний приклад factory method у SAP-стандарті: клієнт отримує cl_salv_table, SAP всередині обирає правильну реалізацію.
  • cl_abap_typedescr=>describe_by_*( ) — фабрика CL_ABAP_*DESCR обʼєктів (кастомна з залежністю від типу).
  • BAdI через cl_badi_manager=>get_badi_ref( ) — теж factory-like.
  • Перегрузка параметрами. Якщо різні конкретні класи вимагають принципово різних параметрів — не ліпи все в один create. Зроби кілька іменованих методів: create_from_file( ), create_from_itab( ), create_from_url( ).
  • Невідомий тип = exception, не null. Ніколи не повертай ro_writer = NULL мовчки — кидай ZCX_UNKNOWN_FORMAT.
  • Абстрактний базовий клас. Якщо використовуєш варіант із методом у базовому класі — зроби його ABSTRACT, щоб випадково не створили NEW lcl_base_writer( ).
  • Не плутай з Abstract Factory. Factory Method створює один тип продукту; Abstract Factoryсімейство повʼязаних продуктів одразу.

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

Section titled “Коли НЕ використовувати”
  • Один клас без альтернатив — просто NEW zcl_foo( ). Не роби фабрику на майбутнє.
  • Коли відмінності між «варіантами» — це просто різні значення параметра. Тоді вистачить NEW zcl_foo( iv_mode = 'A' ).