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

Proxy

Proxy — структурний патерн: обʼєкт-заступник реалізує той самий інтерфейс, що і справжній, і клієнт викликає proxy, не знаючи про це. Proxy додає логіку навколо виклику: лінькова ініціалізація, кеш, перевірки, логування звернень, віддалений виклик.

Ресепшн готелю. Гість просить щось — ресепшн вирішує, кликати прибирання, технічну службу чи менеджера. Або сам відповідає («меню в номері, на столі»). Гість не знає, що запит пішов глибше. І коли справжній виконавець зайнятий, ресепшн може обслужити з кешу: «Так, Wi-Fi-пароль той самий, як вчора».

  • Lazy initialization — важкий обʼєкт створити лише перед першим реальним викликом.
  • Кеш — повертати збережений результат, якщо він є.
  • Контроль доступу — перевіряти повноваження перед проходом до справжнього обʼєкта.
  • Remote proxy — робити RFC/HTTP, приховуючи мережу від клієнта.
  • Logging / metrics — трасувати виклики без правки цільового класу.

ABAP-реалізація — Caching Proxy

Section titled “ABAP-реалізація — Caching Proxy”

Найчастіший варіант у SAP — кеш. Повний код репозиторію матеріалів із кешем:

INTERFACE zif_material_repo.
METHODS get
IMPORTING iv_matnr TYPE matnr
RETURNING VALUE(rs) TYPE zty_material
RAISING zcx_not_found.
ENDINTERFACE.
" Реальний репозиторій — дорогий SELECT
CLASS zcl_material_repo DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES zif_material_repo.
ENDCLASS.
CLASS zcl_material_repo IMPLEMENTATION.
METHOD zif_material_repo~get.
SELECT SINGLE matnr, mtart, meins, brgew
FROM mara
WHERE matnr = @iv_matnr
INTO CORRESPONDING FIELDS OF @rs.
IF sy-subrc <> 0.
RAISE EXCEPTION NEW zcx_not_found( iv_matnr = iv_matnr ).
ENDIF.
ENDMETHOD.
ENDCLASS.
" Caching proxy
CLASS zcl_material_repo_cached DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES zif_material_repo.
METHODS constructor IMPORTING io_real TYPE REF TO zif_material_repo.
PRIVATE SECTION.
DATA:
mo_real TYPE REF TO zif_material_repo,
mt_cache TYPE SORTED TABLE OF zty_material WITH UNIQUE KEY matnr.
ENDCLASS.
CLASS zcl_material_repo_cached IMPLEMENTATION.
METHOD constructor. mo_real = io_real. ENDMETHOD.
METHOD zif_material_repo~get.
READ TABLE mt_cache WITH KEY matnr = iv_matnr ASSIGNING FIELD-SYMBOL(<ls>).
IF sy-subrc = 0.
rs = <ls>.
RETURN. " cache hit
ENDIF.
rs = mo_real->get( iv_matnr ). " cache miss → реальний виклик
INSERT rs INTO TABLE mt_cache.
ENDMETHOD.
ENDCLASS.

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

DATA(lo_repo) = CAST zif_material_repo(
NEW zcl_material_repo_cached( NEW zcl_material_repo( ) ) ).
DATA(ls1) = lo_repo->get( 'MAT001' ). " SELECT
DATA(ls2) = lo_repo->get( 'MAT001' ). " з кешу

Клієнт не знає про кеш. Щоб «вимкнути» кеш у тестах — підсовуємо напряму zcl_material_repo( ).

Важкий обʼєкт не створюється, доки не попросять:

CLASS zcl_heavy_proxy DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION. INTERFACES zif_heavy.
PRIVATE SECTION. DATA mo_real TYPE REF TO zcl_heavy.
ENDCLASS.
CLASS zcl_heavy_proxy IMPLEMENTATION.
METHOD zif_heavy~do_work.
IF mo_real IS NOT BOUND.
mo_real = NEW zcl_heavy( ). " створюємо тільки зараз
ENDIF.
rv = mo_real->do_work( iv_input ).
ENDMETHOD.
ENDCLASS.

Якщо клієнт так і не викличе метод — zcl_heavy взагалі не створиться.

Перевірка повноважень перед викликом:

METHOD zif_repo~delete.
AUTHORITY-CHECK OBJECT 'Z_REPO' ID 'ACTVT' FIELD '06'
ID 'NAME' FIELD iv_id.
IF sy-subrc <> 0.
RAISE EXCEPTION NEW zcx_not_authorized( iv_activity = 'DELETE' ).
ENDIF.
mo_real->delete( iv_id ).
ENDMETHOD.

Структурно однакові. Різниця — у намірі:

  • Proxy — керує доступом (lazy, auth, remote, cache). Клієнт часто не здогадується, що це не реальний обʼєкт.
  • Decorator — додає поведінку (лог, timestamp, формат). Клієнт свідомо обгортає.

Якщо можеш пояснити свою обгортку як «фільтр доступу» — це Proxy. Якщо як «додавання функціональності» — Decorator.

  • RFC Proxy classes (SPROXY, CONSUMER-proxy) — SAP-стандарт genрує proxy для віддалених викликів.
  • cl_abap_proxy_framework — infrastructural proxy для web-servic-і.
  • Authorization proxies довкола BAPI: типове рішення — загортати BAPI у authority-проксі на рівні wrapper-класів.
  • Caching proxy над DAO/model-класом — природне місце, коли метод читає одне й те саме з важкого SELECT кілька разів за сесію.
  • Валідація кешу. Кеш без інвалідації — баг-генератор. Визначи стратегію: TTL? invalidate on write? CLEAR mt_cache на певну подію?
  • Cache coherence. Якщо два обʼєкти мають свій кеш — зміна в одному не відбивається в іншому. Для критичних даних — один загальний кеш (singleton-кеш зі scope-ом на session).
  • Propagation of errors. Proxy має перекидати винятки прозоро, не обгортати у свої, якщо немає додаткового контексту.
  • Якщо proxy тоненький — не городь. Просто виклик, що перевіряє IS BOUND — не потребує окремого класу. Proxy виправданий, коли має свою стабільну інваріантну логіку.

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

Section titled “Коли НЕ використовувати”
  • Дія тривіальна і одноразова — IF mo_obj IS INITIAL. mo_obj = NEW ...( ). ENDIF. не потребує класу.
  • Клієнт хоче бачити і керувати — наприклад, хоче явний cache.clear( ). Тоді потрібен Decorator з додатковими методами, не Proxy.
  • Інтерфейс часто змінюється — proxy доведеться оновлювати щоразу.