Паттерн «Посетитель». Описание и примеры на C# и Java

Паттерн «Посетитель» (Visitor) описывает операцию, выполняемую над каждым объектом из некоторой структуры, без изменения классов этих объектов [1].

Это позволяет добавлять новый функционал без доработки уже имеющихся классов приложения.

Ниже представлена диаграмма классов, которые входят в данный паттерн.

Диаграмма класов паттерна Посетитель

Паттерн «Посетитель» состоит из следующих компонентов:

  • Visitor (Посетитель) — базовый класс, который определяет операцию Visit для каждого объекта ConcretteElement. Имя и сигнатура (или только сигнатура) этой операции позволяет посетителю определить тип класса, с которым он работает;

  • ConcreteVisitor (Конкретный посетитель) — реализует все операции объявленные в классе Visitor;

  • Element (Элемент) — базовый класс, определяющий операцию Accept, которая принимает Visitor в качестве аргумента;

  • ConcreteElement (Конкретный элемент) — реализует операцию Accept;

  • ObjectStructure (Структура объектов) — некоторая структура данных, которая хранит в себе объекты Element и предоставляет к ним доступ.
    В роли такой структуры могут выступать различные списки, массивы, деревья и т.д.

Рассмотрим реализацию паттерна «Посетитель» в C# и Java.

Пример на C#

В качестве примера рассмотрим структуру, которая будет содержать элементы описывающие легковые и грузовые автомобили. Сам пример выполним в виде консольного приложения.

Базовый абстрактный класс элементов Auto содержит одно свойство (название модели ) и один абстрактный метод Accept.

В свою очередь, классы наследники будут реализовывать это метод.

Посетитель будет представлять собой интерфейс, объявляющий методы Visit для легкового и грузового автомобилей.

Конкретный посетитель будет реализовывать данный интерфейс. В качестве примера он будет выводить на экран консоли тип автомобиля и название модели.

Теперь остаётся сформировать структуру данных для экземпляров классов Car и Track и свести все компоненты паттерна вместе в единую программу.

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

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

Данный пример реализован с использованием классического варианта паттерна «Посетитель». В следующем подразделе мы рассмотрим модифицированные приеры.

Пример на Java

На Java мы рассмотрим тот же пример, что и для C#, но с некоторыми изменениями.

Вначале статьи было упоминание о том, что методы Visit у посетителя не обязательно должны различаться названием и сигнатурой. Вполне возможно и различие только в сигнатуре (так называемая, перегрузка методов).

Реализуем приведённый ранее пример для перегруженного метода Visit.

Классы, описывающие автомобили, в данной ситуации почти не изменятся. Если не учитывать характерные особенности Java, единственное отличие заключается в названии вызываемого метода посетителя.

Ниже приведены интерфейс посетителя с перегруженным методом visit и его реализация.

И всё вместе:

В результате получаем программу по своему функционалу полностью аналогичную той, что была написана ранее на C#. Только её архитектура значительно упрощена, так как больше не требуется запоминать названия специфических методов. А, так как Java язык со строгой типизацией вопрос типобезопасности решается самим компилятором.

К слову, C# также поддерживает перегрузку методов и на нём также можно реализовать приведённый вариант паттерна.

Внедрение зависимости (Dependency injection)

Модификация паттерна «Посетитель» не ограничивается перегрузкой методов Visit.

В случае необходимости можно прибегнуть к внедрению зависимости и тем самым сократить внешний интерфейс посетителя до одного метода Visit и ещё более улучшить гибкость объектной модели.

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

В зависимости от выбранного стиля программирования или фреймворка возможны различные варианты реализации внедрения зависимости. В этой статье приведены самые простые из них, которые доступны «штатными средствами» ООП.

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

Один из возможных вариантов внедрения зависимостей на C#:

То же самое на Java:

В результате мы получаем более качественную реализацию паттерна «Посетитель», но с единственной оговоркой. Поведение компонентов этого паттерна при данном подходе может быть корректным только при соблюдении принципов SOLIDи в первую очередь принципа подстановки Барбары Лисков.

Источники
  1. Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приёмы объектно-ориентированного проектирования. Паттерны проектирования.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *