Посетитель (Visitor)

Метафора

 

Данный паттерн можно сравнить с прохождением обследования в больнице. Однако «посетителем» в терминах паттернов здесь будут сами врачи. Чтобы было понятнее: у нас есть больной, которого требуется обследовать и полечить, но так как за разные обследования отвечают разные врачи, то мы просто присылаем к больному врачей в качестве «посетителей». Правило взаимодействия для больного очень простое «пригласите врача (посетителя) чтобы он сделал свою работу», а врач («посетитель») приходит, обследует и делает всё необходимое. Таким образом, следуя простым правилам можно использовать врачей для разных больных по одним и тем же алгоритмам. Как уже было сказано, паттерном «посетитель» в данном случае является врач, который может одинаково обслуживать разные объекты (больных) если его позовут.

 

Назначение

 

Применяя паттерн посетитель, мы можем добавить операции над некоторыми объектами (иерархией объектов), не загрязняя их код, т.е. не расширяя классы.

В классическом представлении GoF, посетитель добавляет операции к некоторой иерархии классов, где в зависимости от типа операции могут отличаться. Однако посетитель может быть применён и не к связанным иерархией классам.

Преимущества:

  • Упрощается добавление новых операций, объединяет родственные операции в классе «Посетитель».

Недостатки:

  • Бизнес логика размазывается между классами иерархии и классами посетителей (нарушается принцип Information Expert из GRASP),
  • Усложнение структуры кода,
  • Если базовый класс посетителя реализует интерфейс с указанием конкретного типа элемента из иерархии, при добавлении нового элемента в иерархию будет затруднительным, т.к. потребует модификации посетителей.

 

Диаграмма

 

Посетитель

Посетитель

 

Некоторая иерархия объектов Element реализует интерфейс с методом Accept, который в свою очередь принимает на вход элемент с интерфейсом Visitor. Внутри метода Accept вызывается метод visit для конкретного типа элемента (VisitElementOne или VisitElementTwo). Передавая тот или иной Visitor, мы тем самым можем выполнять над объектами Element разные операции, не расширяя при этом их интерфейс. Если бы ABAP умел поддерживать перегрузку методов, внутри элементов можно было бы сделать один вызов метода visit (v.Visit(me)), а его реализацию под тот или иной тип объекта уже реализовывать в конкретном посетителе.

 

Пример

 

В нашем примере мы создали двух посетителей, которые выполняют определённые операции над объектами. Каждая отдельная операция свойственна только конкретному типу элемента, соответственно нагружать общую иерархию и выносить это поведение (OperationA, OperationB) на верхний уровень иерархии не имеет смысла.

Так же мы не завязываемся на конкретные типы в интерфейсе посетителя, а оперируем абстракцией, что не столь оптимально, но зато нам не потребуется перестраивать интерфейс посетителя при добавлении в иерархию элементов нового класса.

Еще один пример:

В данном примере у иерархии элементов единый интерфейс, а посетители используются для подсчёта некоторой абстрактной стоимости элемента, причём алгоритмы подсчёта у них разные. Алгоритм не завязан на тип объекта. Если потребуется высчитать некоторую другую стоимость по иному алгоритму, мы не будем нагромождать интерфейс иерархии, а создадим посетителя, который и выполнит нужный нам подсчёт (операцию над элементом).

Еще одна хорошая статья по теме.