Что нового
  • Что бы вступить в ряды "Принятый кодер" Вам нужно:
    Написать 10 полезных сообщений или тем и Получить 10 симпатий.
    Для того кто не хочет терять время,может пожертвовать средства для поддержки сервеса, и вступить в ряды VIP на месяц, дополнительная информация в лс.

  • Пользаватели которые будут спамить, уходят в бан без предупреждения. Спам сообщения определяется администрацией и модератором.

  • Гость, Что бы Вы хотели увидеть на нашем Форуме? Изложить свои идеи и пожелания по улучшению форума Вы можете поделиться с нами здесь. ----> Перейдите сюда
  • Все пользователи не прошедшие проверку электронной почты будут заблокированы. Все вопросы с разблокировкой обращайтесь по адресу электронной почте : info@guardianelinks.com . Не пришло сообщение о проверке или о сбросе также сообщите нам.

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

Sascha

Заместитель Администратора
Команда форума
Администратор
Регистрация
9 Май 2015
Сообщения
1,071
Баллы
155
Возраст
51
Паттерн «Посетитель» (Visitor) описывает операцию, выполняемую над каждым объектом из некоторой структуры, без изменения классов этих объектов [1].

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


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


Пожалуйста Авторизируйтесь или Зарегистрируйтесь для просмотра скрытого текста.



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


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


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


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


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


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

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

Пример на C#


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

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

abstract class Auto
{
public string ModelTitle { get; set; }
public abstract void Accept(IVisitor visitor);
}

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

class Car : Auto
{
public override void Accept(IVisitor visitor)
{
visitor.VisitCar(this);
}
}
class Track : Auto
{
public override void Accept(IVisitor visitor)
{
visitor.VisitTrack(this);
}
}

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

interface Ivisitor
{
void VisitCar(Car car);
void VisitTrack(Track track);
}

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

class AutoVisitor : Ivisitor
{
public void VisitCar(Car car)
{
Console.WriteLine($"Легковой автомобиль модели: {car.ModelTitle}");
}
public void VisitTrack(Track track)
{
Console.WriteLine($"Грузовой автомобиль модели: {track.ModelTitle}");
}
}

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

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

ObservableCollection<Auto> collection = new ObservableCollection<Auto>();
collection.Add(new Car { ModelTitle = "ВАЗ" });
collection.Add(new Track { ModelTitle = "ГАЗель" });
collection.Add(new Car { ModelTitle = "Merсedes" });
collection.Add(new Track { ModelTitle = "КамАЗ" });
IVisitor visitor = new AutoVisitor();
foreach (Auto a in collection)
{
a.Accept(visitor);
}

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

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

Пример на Java


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

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

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

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

public abstract class Auto {
private String modelTitle;
public Auto(String modelTitle) {
this.modelTitle = modelTitle;
}
public abstract void Accept(Visitor visitor);
public String getModelTitle() {
return modelTitle;
}
public void setModelTitle(String modelTitle) {
this.modelTitle = modelTitle;
}
}
public class Car extends Auto {
public Car(String modelTitle) {
super(modelTitle);
}
@Override
public void Accept(Visitor visitor) {
visitor.visit(this);
}
}
public class Track extends Auto {
public Track(String modelTitle) {
super(modelTitle);
}
@Override
public void Accept(Visitor visitor) {
visitor.visit(this);
}
}

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

public interface Visitor {
void visit (Car car);
void visit (Track track);
}
public class AutoVisitor implements Visitor{
@Override
public void visit(Car car) {
System.out.println("Легковой автомобиль модели: "+car.getModelTitle());
}
@Override
public void visit(Track track) {
System.out.println("Грузовой автомобиль модели: "+track.getModelTitle());
}
}

И всё вместе:

ArrayList<Auto> arr = new ArrayList();
arr.add(new Car("ВАЗ"));
arr.add(new Track("ГАЗель"));
arr.add(new Car("Mercedes"));
arr.add(new Track("КамАЗ"));
Visitor visitor = (Visitor) new AutoVisitor();
for(Auto a:arr){
a.Accept(visitor);
}

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

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

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


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

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

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

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

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

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

interface Ivisitor
{
void Visit(Auto auto);
}
class AutoVisitor : Ivisitor
{
public void Visit(Auto auto)
{
string prefix = "Автомобиль модели: ";
if(auto is Car)
{
prefix = "Легковой автомобиль модели: ";
}
if(auto is Track)
{
prefix = "Грузовой автомобиль модели: ";
}
Console.WriteLine($"{prefix}{auto.ModelTitle}");
}
}

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

public interface Visitor {
void visit (Auto auto);
}
public class AutoVisitor implements Visitor{
@Override
public void visit(Auto auto) {
String prefix = "Автомобиль модели:";
if (auto instanceof Car) {
prefix = "Легковой автомобиль модели: ";
}
if (auto instanceof Track) {
prefix = "Грузовой автомобиль модели: ";
}
System.out.println(prefix + auto.getModelTitle());
}
}

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

Источники

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