«1. Обзор

В этом уроке мы познакомимся с одним из поведенческих шаблонов проектирования GoF — посетителем.

Во-первых, мы объясним его цель и проблему, которую он пытается решить.

Далее мы рассмотрим UML-диаграмму посетителя и реализацию практического примера.

2. Шаблон проектирования \»Посетитель\»

Целью шаблона \»Посетитель\» является определение новой операции без внесения изменений в структуру существующего объекта.

Представьте, что у нас есть составной объект, состоящий из компонентов. Структура объекта фиксирована — мы либо не можем ее изменить, либо не планируем добавлять в структуру новые типы элементов.

Теперь, как мы можем добавить новую функциональность в наш код, не изменяя существующие классы?

Шаблон проектирования «Посетитель» может быть ответом. Проще говоря, нам нужно будет добавить функцию, которая принимает класс посетителя к каждому элементу структуры.

Таким образом, наши компоненты позволят реализации посетителя «посетить» их и выполнить любое необходимое действие над этим элементом.

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

Следовательно, мы будем хорошо использовать принцип Open/Closed, поскольку мы не будем изменять код, но мы все равно сможем расширить функциональность, предоставив новую реализацию Visitor.

3. Диаграмма UML

На приведенной выше диаграмме UML у нас есть две иерархии реализации, специализированные посетители и конкретные элементы.

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

Теперь особенно актуально то, что конкретные элементы (ConcreteElementA и ConcreteElementB) принимают посетителя, просто позволяя ему посещать их.

Наконец, этот метод одинаков для всех элементов структуры, он выполняет двойную диспетчеризацию с передачей себя (через ключевое слово this) в метод посещения посетителя.

4. Реализация

Наш пример будет настраиваемым объектом Document, состоящим из конкретных элементов JSON и XML; элементы имеют общий абстрактный суперкласс Element.

Класс Document:

public class Document extends Element {

    List<Element> elements = new ArrayList<>();

    // ...

    @Override
    public void accept(Visitor v) {
        for (Element e : this.elements) {
            e.accept(v);
        }
    }
}

Класс Element имеет абстрактный метод, который принимает интерфейс Visitor:

public abstract void accept(Visitor v);

Поэтому при создании нового элемента назовите его JsonElement, нам придется обеспечить реализацию этого метода.

Однако из-за характера шаблона Посетитель реализация будет такой же, поэтому в большинстве случаев нам потребуется скопировать и вставить шаблонный код из другого, уже существующего элемента:

public class JsonElement extends Element {

    // ...

    public void accept(Visitor v) {
        v.visit(this);
    }
}

Поскольку наши элементы позволяют посещать их любому посетителю, допустим, мы хотим обработать наши элементы Document, но каждый из них по-своему, в зависимости от типа его класса.

Таким образом, у нашего посетителя будет отдельный метод для данного типа:

public class ElementVisitor implements Visitor {

    @Override
    public void visit(XmlElement xe) {
        System.out.println(
          "processing an XML element with uuid: " + xe.uuid);
    }

    @Override
    public void visit(JsonElement je) {
        System.out.println(
          "processing a JSON element with uuid: " + je.uuid);
    }
}

Здесь наш конкретный посетитель реализует два метода, соответственно по одному на каждый тип Элемента.

Это дает нам доступ к конкретному объекту структуры, над которым мы можем выполнять необходимые действия.

5. Тестирование

Для тестирования давайте взглянем на класс VisitorDemo:

public class VisitorDemo {

    public static void main(String[] args) {

        Visitor v = new ElementVisitor();

        Document d = new Document(generateUuid());
        d.elements.add(new JsonElement(generateUuid()));
        d.elements.add(new JsonElement(generateUuid()));
        d.elements.add(new XmlElement(generateUuid()));

        d.accept(v);
    }

    // ...
}

Сначала мы создаем ElementVisitor, он содержит алгоритм, который мы применим к нашим элементам.

Затем мы настраиваем наш документ с соответствующими компонентами и применяем посетителя, который будет принят каждым элементом структуры объекта.

Вывод будет таким:

processing a JSON element with uuid: fdbc75d0-5067-49df-9567-239f38f01b04
processing a JSON element with uuid: 81e6c856-ddaf-43d5-aec5-8ef977d3745e
processing an XML element with uuid: 091bfcb8-2c68-491a-9308-4ada2687e203

Он показывает, что посетитель посетил каждый элемент нашей структуры, в зависимости от типа элемента, он отправил обработку соответствующему методу и может получить данные из каждого базового объекта. .

6. Недостатки

Как и у каждого шаблона проектирования, даже у посетителя есть свои недостатки, в частности, его использование усложняет поддержку кода, если нам нужно добавить новые элементы в структуру объекта.

«Например, если мы добавим новый YamlElement, то нам нужно будет обновить всех существующих посетителей с помощью нового метода, необходимого для обработки этого элемента. Следуя этому далее, если у нас есть десять или более конкретных посетителей, может быть обременительно обновлять их всех.

Помимо этого, при использовании этого шаблона бизнес-логика, относящаяся к одному конкретному объекту, распространяется на все реализации посетителей.

7. Заключение

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

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

Кроме того, посетитель может в конечном итоге агрегировать данные на основе элемента, который он пересекает.

Чтобы увидеть более специализированную версию шаблона проектирования посетителя, ознакомьтесь с шаблоном посетителя в Java NIO — использование шаблона в JDK.

Как обычно, полный код доступен в проекте Github.