Эта статья является частью нашего курса Академии под названием « Шаблоны проектирования Java» .
В этом курсе вы изучите огромное количество шаблонов проектирования и увидите, как они реализуются и используются в Java. Вы поймете причины, почему шаблоны так важны, и узнаете, когда и как применять каждый из них. Проверьте это здесь !
Содержание
1. Введение
Чтобы понять шаблон дизайна посетителя, давайте вернемся к шаблону составного дизайна . Составной шаблон позволяет составлять объекты в древовидные структуры для представления иерархий части-целого.
 В примере Composite Pattern мы создали структуру html, состоящую из объектов разных типов.  Теперь предположим, что нам нужно добавить класс css в теги html.  Один из способов сделать это — добавить класс при добавлении начального тега с setStartTag метода setStartTag .  Но эта жестко заданная настройка создаст негибкость нашего кода. 
  Другой способ сделать это — добавить новый метод, например addClass в родительский абстрактный класс HtmlTag .  Все дочерние классы переопределят этот метод и предоставят класс css .  Одним из основных недостатков этого подхода является то, что, если имеется много дочерних классов (будет на большой HTML-странице), реализация этого метода во всех дочерних классах станет очень дорогой и беспокойной.  И предположим, что позже нам нужно добавить еще один элемент стиля в теги, нам снова нужно сделать то же самое. 
Шаблон проектирования посетителя предоставляет вам возможность добавлять новые операции над объектами, не меняя классов элементов, особенно когда операции меняются довольно часто.
2. Что такое шаблон дизайна посетителя
Целью шаблона проектирования посетителя является представление операции, выполняемой над элементами структуры объекта. Посетитель позволяет определить новую операцию без изменения классов элементов, с которыми он работает.
Шаблон Visitor полезен при разработке операции для разнородной коллекции объектов иерархии классов. Шаблон Visitor позволяет определять операцию без изменения класса любого из объектов в коллекции. Для этого шаблон Visitor предлагает определить операцию в отдельном классе, называемом классом посетителя. Это отделяет операцию от коллекции объектов, с которой она работает. Для каждой новой операции, которая будет определена, создается новый класс посетителей. Поскольку операция должна выполняться над набором объектов, посетителю необходим способ доступа к открытым членам этих объектов. Это требование может быть выполнено путем реализации следующих двух идей дизайна.
посетитель
-   Объявляет операцию 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 | packagecom.javacodegeeks.patterns.visitorpattern;publicinterfaceElement {        publicvoidaccept(Visitor visitor);}packagecom.javacodegeeks.patterns.visitorpattern;publicinterfaceVisitor {    publicvoidvisit(HtmlElement element);    publicvoidvisit(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 | packagecom.javacodegeeks.patterns.visitorpattern;importjava.util.List;publicabstractclassHtmlTag implementsElement{        publicabstractString getTagName();    publicabstractvoidsetStartTag(String tag);    publicabstractString getStartTag();    publicabstractString getEndTag();    publicabstractvoidsetEndTag(String tag);    publicvoidsetTagBody(String tagBody){        thrownewUnsupportedOperationException("Current operation is not support for this object");    }    publicvoidaddChildTag(HtmlTag htmlTag){        thrownewUnsupportedOperationException("Current operation is not support for this object");    }    publicvoidremoveChildTag(HtmlTag htmlTag){        thrownewUnsupportedOperationException("Current operation is not support for this object");    }    publicList<HtmlTag>getChildren(){        thrownewUnsupportedOperationException("Current operation is not support for this object");    }    publicabstractvoidgenerateHtml();} | 
  Абстрактный класс 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 | packagecom.javacodegeeks.patterns.visitorpattern;importjava.util.ArrayList;importjava.util.List;publicclassHtmlParentElement extendsHtmlTag {    privateString tagName;    privateString startTag;     privateString endTag;    privateList<HtmlTag>childrenTag;        publicHtmlParentElement(String tagName){        this.tagName = tagName;        this.startTag = "";        this.endTag = "";        this.childrenTag = newArrayList<>();    }        @Override    publicString getTagName() {        returntagName;    }    @Override    publicvoidsetStartTag(String tag) {        this.startTag = tag;    }    @Override    publicvoidsetEndTag(String tag) {        this.endTag = tag;    }        @Override    publicString getStartTag() {        returnstartTag;    }        @Override    publicString getEndTag() {        returnendTag;    }        @Override    publicvoidaddChildTag(HtmlTag htmlTag){        childrenTag.add(htmlTag);    }        @Override    publicvoidremoveChildTag(HtmlTag htmlTag){        childrenTag.remove(htmlTag);    }        @Override    publicList<HtmlTag>getChildren(){        returnchildrenTag;    }    @Override    publicvoidgenerateHtml() {        System.out.println(startTag);        for(HtmlTag tag : childrenTag){            tag.generateHtml();        }        System.out.println(endTag);            }    @Override    publicvoidaccept(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 | packagecom.javacodegeeks.patterns.visitorpattern;publicclassHtmlElement extendsHtmlTag{    privateString tagName;    privateString startTag;     privateString endTag;    privateString tagBody;        publicHtmlElement(String tagName){        this.tagName = tagName;        this.tagBody = "";        this.startTag = "";        this.endTag = "";    }        @Override    publicString getTagName() {        returntagName;    }    @Override    publicvoidsetStartTag(String tag) {        this.startTag = tag;    }        @Override    publicvoidsetEndTag(String tag) {        this.endTag = tag;    }        @Override    publicString getStartTag() {        returnstartTag;    }        @Override    publicString getEndTag() {        returnendTag;    }        @Override    publicvoidsetTagBody(String tagBody){        this.tagBody = tagBody;    }        @Override    publicvoidgenerateHtml() {        System.out.println(startTag+""+tagBody+""+endTag);    }    @Override    publicvoidaccept(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 | packagecom.javacodegeeks.patterns.visitorpattern;publicclassCssClassVisitor implementsVisitor{    @Override    publicvoidvisit(HtmlElement element) {        element.setStartTag(element.getStartTag().replace(">", " class='visitor'>"));            }    @Override    publicvoidvisit(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 | packagecom.javacodegeeks.patterns.visitorpattern;publicclassStyleVisitor implementsVisitor {    @Override    publicvoidvisit(HtmlElement element) {        element.setStartTag(element.getStartTag().replace(">", " style='width:46px;'>"));            }    @Override    publicvoidvisit(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 | packagecom.javacodegeeks.patterns.visitorpattern;publicclassTestVisitorPattern {        publicstaticvoidmain(String[] args) {                System.out.println("Before visitor......... \\n");                HtmlTag parentTag = newHtmlParentElement("<html>");        parentTag.setStartTag("<html>");        parentTag.setEndTag("</html>");                HtmlTag p1 = newHtmlParentElement("<body>");        p1.setStartTag("<body>");        p1.setEndTag("</body>");                parentTag.addChildTag(p1);                HtmlTag child1 = newHtmlElement("<p>");        child1.setStartTag("<p>");        child1.setEndTag("</p>");        child1.setTagBody("Testing html tag library");        p1.addChildTag(child1);                child1 = newHtmlElement("<p>");        child1.setStartTag("<p>");        child1.setEndTag("</p>");        child1.setTagBody("Paragraph 2");        p1.addChildTag(child1);                parentTag.generateHtml();                System.out.println("\\nAfter visitor....... \\n");                Visitor cssClass = newCssClassVisitor();        Visitor style = newStyleVisitor();                parentTag = newHtmlParentElement("<html>");        parentTag.setStartTag("<html>");        parentTag.setEndTag("</html>");        parentTag.accept(style);        parentTag.accept(cssClass);                p1 = newHtmlParentElement("<body>");        p1.setStartTag("<body>");        p1.setEndTag("</body>");        p1.accept(style);        p1.accept(cssClass);                parentTag.addChildTag(p1);                child1 = newHtmlElement("<p>");        child1.setStartTag("<p>");        child1.setEndTag("</p>");        child1.setTagBody("Testing html tag library");        child1.accept(style);        child1.accept(cssClass);                p1.addChildTag(child1);                child1 = newHtmlElement("<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
