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

Visitor

Visitor — поведінковий патерн, що дозволяє додавати нові операції над ієрархією обʼєктів без зміни самих класів. Новий алгоритм — новий клас-відвідувач (visitor). Обʼєкти ієрархії мають метод accept( visitor ), що передає керування назад у visitor через метод, специфічний для конкретного типу (подвійний диспетчер).

Екскурсовод у музеї. Самі експонати не змінюються, але можна прийти з різними гідами: гід-історик розповість одне, гід-інженер — інше, гід-мистецтвознавець — третє. Експонати (receivers) приймають гідів (visitors) і «показують себе» — кожен гід інтерпретує по-своєму.

  • Є стабільна ієрархія обʼєктів (наприклад, типи документів SAP, типи фінансових транзакцій).
  • Потрібно додавати нові операції (експорт у XML, JSON, валідація, підрахунок агрегатів) — частіше, ніж нові типи обʼєктів.
  • Розкидати методи по ієрархії — порушує інкапсуляцію або змішує непоєднані обовʼязки.
" Visitor — знає, як працювати з кожним типом
INTERFACE zif_shape_visitor.
METHODS:
visit_circle IMPORTING io_circle TYPE REF TO zcl_circle,
visit_rectangle IMPORTING io_rectangle TYPE REF TO zcl_rectangle,
visit_triangle IMPORTING io_triangle TYPE REF TO zcl_triangle.
ENDINTERFACE.
" Ієрархія — кожен елемент має accept( )
INTERFACE zif_shape.
METHODS accept IMPORTING io_visitor TYPE REF TO zif_shape_visitor.
ENDINTERFACE.
CLASS zcl_circle DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES zif_shape.
METHODS constructor IMPORTING iv_radius TYPE i.
DATA mv_radius TYPE i READ-ONLY.
ENDCLASS.
CLASS zcl_circle IMPLEMENTATION.
METHOD constructor. mv_radius = iv_radius. ENDMETHOD.
METHOD zif_shape~accept.
io_visitor->visit_circle( me ). " подвійний диспетчер
ENDMETHOD.
ENDCLASS.
" Конкретний visitor — обчислює площу
CLASS zcl_area_visitor DEFINITION PUBLIC FINAL CREATE PUBLIC.
PUBLIC SECTION.
INTERFACES zif_shape_visitor.
METHODS get_total RETURNING VALUE(rv) TYPE f.
PRIVATE SECTION.
DATA mv_total TYPE f.
ENDCLASS.
CLASS zcl_area_visitor IMPLEMENTATION.
METHOD zif_shape_visitor~visit_circle.
mv_total = mv_total + '3.14159' * io_circle->mv_radius ** 2.
ENDMETHOD.
METHOD zif_shape_visitor~visit_rectangle.
mv_total = mv_total + io_rectangle->mv_width * io_rectangle->mv_height.
ENDMETHOD.
METHOD zif_shape_visitor~visit_triangle.
mv_total = mv_total + io_triangle->mv_base * io_triangle->mv_height / 2.
ENDMETHOD.
METHOD get_total. rv = mv_total. ENDMETHOD.
ENDCLASS.

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

DATA lt_shapes TYPE STANDARD TABLE OF REF TO zif_shape.
APPEND NEW zcl_circle( iv_radius = 5 ) TO lt_shapes.
APPEND NEW zcl_rectangle( iv_width = 4 iv_height = 3 ) TO lt_shapes.
DATA(lo_area) = NEW zcl_area_visitor( ).
LOOP AT lt_shapes INTO DATA(lo_shape).
lo_shape->accept( lo_area ).
ENDLOOP.
WRITE: / 'Total area:', lo_area->get_total( ).

Новий алгоритм — новий клас, наприклад zcl_export_svg_visitor, zcl_validation_visitor. Класи ієрархії (zcl_circle, zcl_rectangle) не змінюються.

Подвійний диспетчер — чому

Section titled “Подвійний диспетчер — чому”

В ABAP немає method overloading за runtime-типом. io_visitor->visit( me ) з me : zcl_circle викличе visit_shape, не visit_circle. Щоб диспетчер враховував обидва типи (і visitor, і shape), треба два виклики:

  1. Клієнт → shape->accept( visitor ) — диспетчер по типу shape.
  2. Всередині acceptvisitor->visit_circle( me ) — диспетчер по типу visitor.

Це і є «подвійний диспетчер».

  • iXML DOM traversal через if_ixml_node_iterator + свій visitor — типове застосування для обробки різних типів XML-нод.
  • AST-подібні обходи (ABAP Test Cockpit, Code Inspector) використовують visitor-like механізм для перевірки різних типів конструкцій.
  • cl_abap_typedescr ієрархія + методи is_a/describe_by_* — імітація visitor без формального інтерфейсу. Коли в коді зʼявляється CASE type_kind ... WHEN cl_abap_typedescr=>typekind_struct. WHEN typekind_table. — це кандидат на рефакторинг у Visitor.
  • Розподіл даних. Visitor має діставатись до даних обʼєктів ієрархії — або через public getter-и (ламає інкапсуляцію), або через GLOBAL FRIENDS. Вибирай свідомо.
  • Вибух методів у visitor-і. Ієрархія з 10 типами = 10 методів у кожному visitor-і. Якщо visitor реалізує лише один — інші методи все одно мусить оголошувати (можна через абстрактний базовий visitor з порожнім дефолтом).
  • Складність для новачка. Потік керування у Visitor не очевидний: acceptvisit_* — треба перечитати двічі. Коментар біля accept( ) зберігає час колегам.

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

Section titled “Коли НЕ використовувати”
  • Ієрархія часто розширюється — Visitor стане тягарем.
  • Операцій мало і вони природні для самих обʼєктів (наприклад, area( ) у геометрії — частина обʼєкта, не зовнішня операція).
  • Немає потреби додавати нові операції ззовні — не ускладнюй.