Статьи

Пример шаблона дизайна посетителя

Эта статья является частью нашего курса Академии под названием « Шаблоны проектирования Java» .

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

1. Введение

Чтобы понять шаблон дизайна посетителя, давайте вернемся к шаблону составного дизайна . Составной шаблон позволяет составлять объекты в древовидные структуры для представления иерархий части-целого.

В примере Composite Pattern мы создали структуру html, состоящую из объектов разных типов. Теперь предположим, что нам нужно добавить класс css в теги html. Один из способов сделать это — добавить класс при добавлении начального тега с setStartTag метода setStartTag . Но эта жестко заданная настройка создаст негибкость нашего кода.

Другой способ сделать это — добавить новый метод, например addClass в родительский абстрактный класс HtmlTag . Все дочерние классы переопределят этот метод и предоставят класс css . Одним из основных недостатков этого подхода является то, что, если имеется много дочерних классов (будет на большой HTML-странице), реализация этого метода во всех дочерних классах станет очень дорогой и беспокойной. И предположим, что позже нам нужно добавить еще один элемент стиля в теги, нам снова нужно сделать то же самое.

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

2. Что такое шаблон дизайна посетителя

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

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

Рисунок 1 - Шаблон дизайна посетителя Диаграмма классов

Рисунок 1 — Шаблон дизайна посетителя Диаграмма классов

посетитель

  • Объявляет операцию Visit для каждого класса ConcreteElement в структуре объекта. Имя и подпись операции идентифицируют класс, который отправляет запрос на visitor . Это позволяет посетителю определить конкретный класс посещаемого элемента. Затем посетитель может получить доступ к элементу напрямую через его конкретный интерфейс.

ConcreteVisitor

  • Реализует каждую операцию, объявленную Visitor . Каждая операция реализует фрагмент алгоритма, определенный для соответствующего класса объекта в структуре. ConcreteVisitor предоставляет контекст для алгоритма и сохраняет его локальное состояние. Это состояние часто накапливает результаты во время обхода структуры.

Элемент

  • Определяет операцию Accept которая принимает visitor в качестве аргумента.

ConcreteElement

  • Реализует операцию Accept которая принимает visitor в качестве аргумента.

ObjectStructure

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

3. Реализовать шаблон дизайна посетителя

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

Реализация шаблона посетителя требует двух важных интерфейсов, интерфейса Element который будет содержать метод accept с аргументом типа Visitor . Этот интерфейс будет реализован всеми классами, которые должны позволять посетителям посещать их. В нашем случае HtmlTag будет реализовывать интерфейс Element , так как HtmlTag является родительским абстрактным классом всех конкретных html-классов, конкретные классы будут наследовать и переопределять метод accept интерфейса Element .

Другим важным интерфейсом является интерфейс Visitor ; этот интерфейс будет содержать методы посещения с аргументом класса, который реализует интерфейс Element . Также обратите внимание, что мы добавили два новых метода в наш класс HtmlTag , getStartTag и getEndTag в отличие от примера, показанного в уроке «Составной шаблон проектирования».

01
02
03
04
05
06
07
08
09
10
11
12
13
package com.javacodegeeks.patterns.visitorpattern;
 
public interface Element {
     
    public void accept(Visitor visitor);
}
 
package com.javacodegeeks.patterns.visitorpattern;
 
public interface Visitor {
    public void visit(HtmlElement element);
    public void visit(HtmlParentElement parentElement);
}

Код ниже взят из примера Composite Pattern с несколькими изменениями.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.javacodegeeks.patterns.visitorpattern;
 
import java.util.List;
 
 
public abstract class HtmlTag implements Element{
     
    public abstract String getTagName();
    public abstract void setStartTag(String tag);
    public abstract String getStartTag();
    public abstract String getEndTag();
    public abstract void setEndTag(String tag);
    public void setTagBody(String tagBody){
        throw new UnsupportedOperationException("Current operation is not support for this object");
    }
    public void addChildTag(HtmlTag htmlTag){
        throw new UnsupportedOperationException("Current operation is not support for this object");
    }
    public void removeChildTag(HtmlTag htmlTag){
        throw new UnsupportedOperationException("Current operation is not support for this object");
    }
    public List<HtmlTag>getChildren(){
        throw new UnsupportedOperationException("Current operation is not support for this object");
    }
    public abstract void generateHtml();
 
}

Абстрактный класс HtmlTag реализует интерфейс Element . Приведенные ниже конкретные классы переопределят метод accept интерфейса Element , вызовут метод visit и передадут this оператор в качестве аргумента. Это позволит методу visitor получать все открытые члены объекта, добавлять новые операции над ним.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package com.javacodegeeks.patterns.visitorpattern;
 
import java.util.ArrayList;
import java.util.List;
 
public class HtmlParentElement extends HtmlTag {
 
    private String tagName;
    private String startTag;
    private String endTag;
    private List<HtmlTag>childrenTag;
     
    public HtmlParentElement(String tagName){
        this.tagName = tagName;
        this.startTag = "";
        this.endTag = "";
        this.childrenTag = new ArrayList<>();
    }
     
    @Override
    public String getTagName() {
        return tagName;
    }
 
    @Override
    public void setStartTag(String tag) {
        this.startTag = tag;
    }
 
    @Override
    public void setEndTag(String tag) {
        this.endTag = tag;
    }
     
    @Override
    public String getStartTag() {
        return startTag;
    }
     
    @Override
    public String getEndTag() {
        return endTag;
    }
     
    @Override
    public void addChildTag(HtmlTag htmlTag){
        childrenTag.add(htmlTag);
    }
     
    @Override
    public void removeChildTag(HtmlTag htmlTag){
        childrenTag.remove(htmlTag);
    }
     
    @Override
    public List<HtmlTag>getChildren(){
        return childrenTag;
    }
 
    @Override
    public void generateHtml() {
        System.out.println(startTag);
        for(HtmlTag tag : childrenTag){
            tag.generateHtml();
        }
        System.out.println(endTag);
         
    }
 
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.javacodegeeks.patterns.visitorpattern;
 
public class HtmlElement extends HtmlTag{
 
    private String tagName;
    private String startTag;
    private String endTag;
    private String tagBody;
     
    public HtmlElement(String tagName){
        this.tagName = tagName;
        this.tagBody = "";
        this.startTag = "";
        this.endTag = "";
    }
     
    @Override
    public String getTagName() {
        return tagName;
    }
 
    @Override
    public void setStartTag(String tag) {
        this.startTag = tag;
    }
     
    @Override
    public void setEndTag(String tag) {
        this.endTag = tag;
    }
     
    @Override
    public String getStartTag() {
        return startTag;
    }
     
    @Override
    public String getEndTag() {
        return endTag;
    }
     
    @Override
    public void setTagBody(String tagBody){
        this.tagBody = tagBody;
    }
     
    @Override
    public void generateHtml() {
        System.out.println(startTag+""+tagBody+""+endTag);
    }
 
    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
 
}

Теперь, конкретные классы посетителей: мы создали два конкретных класса, один добавит visitor класса css ко всем тегам html, а другой изменит ширину тега, используя атрибут style тега html.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package com.javacodegeeks.patterns.visitorpattern;
 
public class CssClassVisitor implements Visitor{
 
    @Override
    public void visit(HtmlElement element) {
        element.setStartTag(element.getStartTag().replace(">", " class='visitor'>"));
         
    }
 
    @Override
    public void visit(HtmlParentElement parentElement) {
        parentElement.setStartTag(parentElement.getStartTag().replace(">", " class='visitor'>"));
    }
 
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
package com.javacodegeeks.patterns.visitorpattern;
 
public class StyleVisitor implements Visitor {
 
    @Override
    public void visit(HtmlElement element) {
        element.setStartTag(element.getStartTag().replace(">", " style='width:46px;'>"));
         
    }
 
    @Override
    public void visit(HtmlParentElement parentElement) {
        parentElement.setStartTag(parentElement.getStartTag().replace(">", " style='width:58px;'>"));
    }
 
}

Теперь давайте проверим приведенный выше пример.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
package com.javacodegeeks.patterns.visitorpattern;
 
public class TestVisitorPattern {
     
    public static void main(String[] args) {
         
        System.out.println("Before visitor......... \\n");
         
        HtmlTag parentTag = new HtmlParentElement("<html>");
        parentTag.setStartTag("<html>");
        parentTag.setEndTag("</html>");
         
        HtmlTag p1 = new HtmlParentElement("<body>");
        p1.setStartTag("<body>");
        p1.setEndTag("</body>");
         
        parentTag.addChildTag(p1);
         
        HtmlTag child1 = new HtmlElement("<p>");
        child1.setStartTag("<p>");
        child1.setEndTag("</p>");
        child1.setTagBody("Testing html tag library");
        p1.addChildTag(child1);
         
        child1 = new HtmlElement("<p>");
        child1.setStartTag("<p>");
        child1.setEndTag("</p>");
        child1.setTagBody("Paragraph 2");
        p1.addChildTag(child1);
         
        parentTag.generateHtml();
         
        System.out.println("\\nAfter visitor....... \\n");
         
        Visitor cssClass = new CssClassVisitor();
        Visitor style = new StyleVisitor();
         
        parentTag = new HtmlParentElement("<html>");
        parentTag.setStartTag("<html>");
        parentTag.setEndTag("</html>");
        parentTag.accept(style);
        parentTag.accept(cssClass);
         
        p1 = new HtmlParentElement("<body>");
        p1.setStartTag("<body>");
        p1.setEndTag("</body>");
        p1.accept(style);
        p1.accept(cssClass);
         
        parentTag.addChildTag(p1);
         
        child1 = new HtmlElement("<p>");
        child1.setStartTag("<p>");
        child1.setEndTag("</p>");
        child1.setTagBody("Testing html tag library");
        child1.accept(style);
        child1.accept(cssClass);
         
        p1.addChildTag(child1);
         
        child1 = new HtmlElement("<p>");
        child1.setStartTag("<p>");
        child1.setEndTag("</p>");
        child1.setTagBody("Paragraph 2");
        child1.accept(style);
        child1.accept(cssClass);
         
        p1.addChildTag(child1);
         
        parentTag.generateHtml();
    }
 
}

Приведенный выше код приведет к следующему выводу:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
Before visitor.........
 
<html>
<body>
<p>Testing html tag library</p>
<p>Paragraph 2</p>
</body>
</html>
 
After visitor.......
 
<html style='width:58px;' class='visitor'>
<body style='width:58px;' class='visitor'>
<p style='width:46px;' class='visitor'>Testing html tag library</p>
<p style='width:46px;' class='visitor'>Paragraph 2</p>
</body>
</html>

Результат после «До посетителя…» совпадает с результатом урока «Составной шаблон». Позже мы создали двух конкретных посетителей, а затем добавили их в конкретные html-объекты с помощью метода accept . Вывод «После посетителя…» показывает результат, в котором элементы класса и стиля css добавляются в теги html.

Обратите внимание, что преимущество шаблона посетителя состоит в том, что мы можем добавлять новые операции к объекту без изменения его классов. Например, мы можем добавить некоторые функции javascript, onclick как onclick или несколько тегов angularjs ng, без изменения классов.

4. Когда использовать шаблон дизайна посетителя

Используйте шаблон «Посетитель», когда:

  • Структура объекта содержит много классов объектов с различными интерфейсами, и вы хотите выполнять операции над этими объектами, которые зависят от их конкретных классов.
  • Множество различных и не связанных операций необходимо выполнять над объектами в структуре объекта, и вы хотите избежать «загрязнения» их классов этими операциями. Посетитель позволяет объединять связанные операции, определяя их в одном классе. Когда структура объекта используется многими приложениями, используйте Visitor, чтобы поместить операции только в те приложения, которые в них нуждаются.
  • Классы, определяющие структуру объекта, редко меняются, но вы часто хотите определить новые операции над структурой. Изменение классов структуры объекта требует переопределения интерфейса для всех посетителей, что может быть дорогостоящим. Если классы структуры объектов часто меняются, то, вероятно, лучше определить операции в этих классах.

5. Шаблон дизайна посетителя в JDK

  • javax.lang.model.element.Element и javax.lang.model.element.ElementVisitor
  • javax.lang.model.type.TypeMirror и javax.lang.model.type.TypeVisitor

6. Загрузите исходный код

Это был урок по шаблону дизайна посетителя. Вы можете скачать соответствующий исходный код здесь: VisitorPattern-Project