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

Singleton

Singleton — породжуючий патерн, що гарантує: на всю runtime-сесію існує єдиний екземпляр класу, і доступ до нього — через глобальну точку (найчастіше CLASS-METHOD get_instance( )).

Телефонна станція міста — одна. Скільки б абонентів не набирало номер, вони всі проходять через ту саму станцію.

  • Ресурс, що має існувати в однині: кеш конфігурації, логер, менеджер зʼєднань з зовнішнім сервісом.
  • Координація дій через один обʼєкт (черга, лічильник, реєстр).
  • Важкий обʼєкт з ліньковим створенням — створити один раз, перевикористовувати.

Ключові елементи: CREATE PRIVATE (заборона прямого NEW), статичний holder, статичний фабричний метод.

CLASS zcl_config DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS get_instance
RETURNING VALUE(ro_instance) TYPE REF TO zcl_config.
METHODS get_value
IMPORTING iv_key TYPE string
RETURNING VALUE(rv_value) TYPE string
RAISING zcx_config_not_found.
PRIVATE SECTION.
CLASS-DATA go_instance TYPE REF TO zcl_config.
METHODS constructor.
DATA mt_values TYPE SORTED TABLE OF ty_kv WITH UNIQUE KEY key.
ENDCLASS.
CLASS zcl_config IMPLEMENTATION.
METHOD get_instance.
IF go_instance IS INITIAL.
go_instance = NEW #( ).
ENDIF.
ro_instance = go_instance.
ENDMETHOD.
METHOD constructor.
" разова ініціалізація: завантаження TVARVC, Customizing, змінних середовища
ENDMETHOD.
METHOD get_value.
READ TABLE mt_values WITH KEY key = iv_key ASSIGNING FIELD-SYMBOL(<ls>).
IF sy-subrc <> 0.
RAISE EXCEPTION NEW zcx_config_not_found( iv_key = iv_key ).
ENDIF.
rv_value = <ls>-value.
ENDMETHOD.
ENDCLASS.

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

DATA(lv_host) = zcl_config=>get_instance( )->get_value( 'API_HOST' ).

На рівні однієї internal session (один work process, один діалоговий крок) — перевірка IF go_instance IS INITIAL безпечна: work process однопотоковий. Але глобального між work-процесами стану у singleton немає — CLASS-DATA живе у межах internal session. Якщо треба справжньо глобальний кеш — EXPORT TO SHARED MEMORY або SHMA/SHMM-обʼєкти.

  • Більшість released API-фасадів у S/4HANA — по суті singleton-и через статичну фабрику (наприклад, cl_abap_syst=>get_instance( )).
  • cl_badi_manager — singleton-менеджер BAdI.
  • Для справжньо глобального між-сесійного кешу SAP дає Shared Memory Objects (клас cl_shm_area, TX SHMA). Це — не singleton у GoF-сенсі, а інша сутність.
  • Прихована залежність. zcl_config=>get_instance( ) всередині методу — це залежність, яку видно тільки у коді. У сигнатурі її нема. Колега прочитає METHODS process IMPORTING iv_matnr TYPE matnr і не здогадається, що метод лізе у глобальний конфіг.
  • Відсутність мока. Якщо get_instance повертає конкретний клас — замінити його в тесті можна тільки через LOCAL FRIENDS + присвоєння go_instance напряму. Див. backdoor injection.
  • Порядок ініціалізації. Якщо конструктор singleton-у лізе в інший singleton — ти привʼязуєшся до порядку створення. Дедлоки рідкі, але бувають.
Проблема singleton-уЩо робити замість
Глобальний стан ускладнює тестиConstructor injection + передача інстансу як параметра
Треба один обʼєкт на запит (не на сесію)Dependency Injection container — у ABAP цього нема, найближче — фабрика, яку передають як параметр
Треба кілька іменованих інстансівMultiton — див. нижче
Треба глобально-глобально (між сесіями)Shared Memory Objects (cl_shm_area)

Як singleton, але кілька іменованих екземплярів — по одному на ключ:

CLASS zcl_cache DEFINITION PUBLIC FINAL CREATE PRIVATE.
PUBLIC SECTION.
CLASS-METHODS get_instance
IMPORTING iv_name TYPE string
RETURNING VALUE(ro_inst) TYPE REF TO zcl_cache.
PRIVATE SECTION.
TYPES: BEGIN OF ty_reg,
name TYPE string,
instance TYPE REF TO zcl_cache,
END OF ty_reg.
CLASS-DATA gt_registry TYPE HASHED TABLE OF ty_reg WITH UNIQUE KEY name.
ENDCLASS.
CLASS zcl_cache IMPLEMENTATION.
METHOD get_instance.
READ TABLE gt_registry WITH KEY name = iv_name ASSIGNING FIELD-SYMBOL(<ls>).
IF sy-subrc <> 0.
INSERT VALUE #( name = iv_name instance = NEW #( ) ) INTO TABLE gt_registry
ASSIGNING <ls>.
ENDIF.
ro_inst = <ls>-instance.
ENDMETHOD.
ENDCLASS.

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

Section titled “Коли НЕ використовувати”
  • Якщо клас вирішує бізнес-задачу — майже ніколи. Зробить тести нестерпними.
  • Якщо singleton зʼявився тому, що «лінь передавати через конструктор» — це запах.
  • Якщо інстансів насправді треба кілька (наприклад, окремий логер на підсистему) — бери Factory або Multiton.