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

Observer

Observer — поведінковий патерн: обʼєкт-subject підтримує список спостерігачів і сповіщає їх про зміни свого стану. У ABAP для цього є вбудовані events — класовий механізм, що звільняє від необхідності руками писати реєстр спостерігачів.

Підписка на канал: автор публікує пост → всі підписники отримують сповіщення. Автор не знає, хто саме підписаний — він просто робить publish. Підписник не знає, як саме автор стежить за ним — він просто отримує сповіщення на SET HANDLER.

  • Зміна стану одного обʼєкта має тригерити побічні дії в кількох інших (лог, аудит, оновлення UI, відправка notification).
  • Обʼєкт-джерело не повинен знати про конкретних отримувачів — тільки факт «щось трапилось».
  • Потрібна динамічна підписка/відписка — у runtime.
  • Хочеш уникнути прямих залежностей subject → observer1, observer2, observer3.

ABAP-реалізація — через events

Section titled “ABAP-реалізація — через events”

ABAP events — найчистіша форма Observer у мові. Обʼєкт-джерело декларує EVENTS, підписники реєструють handler через SET HANDLER.

CLASS zcl_order DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
EVENTS:
confirmed EXPORTING VALUE(ev_order_id) TYPE zty_order_id,
cancelled EXPORTING VALUE(ev_order_id) TYPE zty_order_id
VALUE(ev_reason) TYPE string.
METHODS:
constructor IMPORTING iv_id TYPE zty_order_id,
confirm,
cancel IMPORTING iv_reason TYPE string.
PRIVATE SECTION.
DATA mv_id TYPE zty_order_id.
ENDCLASS.
CLASS zcl_order IMPLEMENTATION.
METHOD constructor. mv_id = iv_id. ENDMETHOD.
METHOD confirm.
" ... бізнес-логіка ...
RAISE EVENT confirmed EXPORTING ev_order_id = mv_id.
ENDMETHOD.
METHOD cancel.
" ... бізнес-логіка ...
RAISE EVENT cancelled EXPORTING ev_order_id = mv_id
ev_reason = iv_reason.
ENDMETHOD.
ENDCLASS.

Підписник (наприклад, аудит-логер):

CLASS zcl_audit_logger DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
METHODS:
on_confirmed FOR EVENT confirmed OF zcl_order
IMPORTING ev_order_id,
on_cancelled FOR EVENT cancelled OF zcl_order
IMPORTING ev_order_id ev_reason.
ENDCLASS.
CLASS zcl_audit_logger IMPLEMENTATION.
METHOD on_confirmed.
" INSERT zaudit VALUES ( order = ev_order_id action = 'CONFIRM' ts = ... )
ENDMETHOD.
METHOD on_cancelled.
" INSERT zaudit VALUES ( ... action = 'CANCEL' reason = ev_reason )
ENDMETHOD.
ENDCLASS.

Підключення:

DATA(lo_order) = NEW zcl_order( iv_id = '4711' ).
DATA(lo_logger) = NEW zcl_audit_logger( ).
SET HANDLER lo_logger->on_confirmed FOR lo_order.
SET HANDLER lo_logger->on_cancelled FOR lo_order.
lo_order->confirm( ). " → lo_logger->on_confirmed виконається

Відписка: SET HANDLER lo_logger->on_confirmed FOR lo_order ACTIVATION abap_false.

Або одразу для всіх субʼєктів класу: SET HANDLER ... FOR ALL INSTANCES.

Якщо EVENTS ... FOR ALL INSTANCES або CLASS-EVENTS — подія статична, підписка одна на весь клас. Використовуй для системних подій («будь-який order скасовано»), не для конкретного обʼєкта.

Коли events незручні (треба програмно перебирати підписників, контролювати порядок), зроби реєстр руками:

CLASS zcl_subject DEFINITION.
PUBLIC SECTION.
METHODS:
attach IMPORTING io_observer TYPE REF TO zif_observer,
detach IMPORTING io_observer TYPE REF TO zif_observer,
notify IMPORTING is_payload TYPE zty_payload.
PRIVATE SECTION.
DATA mt_observers TYPE STANDARD TABLE OF REF TO zif_observer WITH EMPTY KEY.
ENDCLASS.
CLASS zcl_subject IMPLEMENTATION.
METHOD notify.
LOOP AT mt_observers INTO DATA(lo).
lo->update( is_payload ).
ENDLOOP.
ENDMETHOD.
ENDCLASS.

Використовуй, коли треба:

  • контролювати порядок сповіщення;
  • фільтрувати по типу/предикату;
  • зупиняти нотифікацію після першого успіху.
  • cl_gui_alv_grid + user-command events — канонічне місце SET HANDLER.
  • cl_gui_textedit, cl_gui_html_viewer — весь GUI-стек працює на events.
  • cl_salv_events_table — події SALV (click, double_click, user_command) підписуються через SET HANDLER.
  • Application Events у RAP — determination-и реагують на події CRUD.
  • Business Events (tx SWEL, SWETYPV) — old-school, але досі зустрічається.
  • Витоки памʼяті. Якщо обʼєкт-subject тримає handler на обʼєкт-observer, а сам observer вже «вийшов зі сцени» — GC не прибере його, бо є зовнішнє посилання. У довгоживучих subject-ах явно SET HANDLER ... ACTIVATION abap_false або DEACTIVATE EVENT.
  • Винятки у handler-і. Якщо один handler кинув exception, наступні handler-и можуть не виконатися — залежить від механізму (events у ABAP перериваються на першому unhandled).
  • Синхронність. ABAP events — синхронні. RAISE EVENT блокує subject, доки всі handler-и не відпрацюють. Для «послав і забув» — використовуй Update task (CALL FUNCTION ... IN UPDATE TASK) або фонове завдання.
  • Не ланцюжи події нескінченно. Якщо handler змінює subject, що знову кидає подію, яка знову тригерить handler — рекурсія. ABAP не захищає автоматично.
  • Не плутай з publish-subscribe через message broker. ABAP events — in-process. Для міжсесійного messaging — qRFC/tRFC, BgRFC, Event Mesh.

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

Section titled “Коли НЕ використовувати”
  • Тільки один підписник, відомий статично — прямий виклик зрозуміліший.
  • Підписка не змінюється — IF/CASE з явним викликом тест-пригожіший і легше дебажити.
  • Потрібна гарантована послідовність і обробка помилок — події у ABAP дають мало контролю.