1. Общ преглед
В този урок ще представим един от поведенческите модели на GoF дизайн - Visitor.
Първо ще обясним неговата цел и проблема, който се опитва да реши.
След това ще разгледаме UML диаграмата на посетителя и изпълнението на практическия пример.
2. Модел за дизайн на посетители
Целта на модел на посетител е да дефинира нова операция, без да въвежда модификациите в съществуваща обектна структура.
Представете си, че имаме композитобект, който се състои от компоненти. Структурата на обекта е фиксирана - или не можем да я променим, или не планираме да добавяме нови видове елементи към структурата.
Сега, как бихме могли да добавим нова функционалност към нашия код без промяна на съществуващи класове?
Моделът за дизайн на посетителя може да е отговор. Най-просто казано, ще трябва да добавим функция, която приема клас посетител към всеки елемент от структурата.
По този начин нашите компоненти ще позволят на реализацията на посетителя да ги „посети“ и да извърши всяко необходимо действие върху този елемент.
С други думи, ще извлечем алгоритъма, който ще бъде приложен към обектната структура от класовете.
Следователно ще използваме добре принципа Open / Closed, тъй като няма да модифицираме кода, но все пак ще можем да разширим функционалността, като предоставим нова реализация на Visitor .
3. UML диаграма

На UML диаграмата по-горе имаме две йерархии на изпълнение, специализирани посетители и конкретни елементи.
На първо място, клиентът използва реализация на Visitor и я прилага към структурата на обекта. Композитният обект се преразглежда по своите компоненти и прилага посетителя към всеки от тях.
Сега особено важно е, че конкретни елементи (ConcreteElementA и ConcreteElementB) приемат Посетител, като просто му позволяват да ги посети .
И накрая, този метод е един и същ за всички елементи в структурата, той извършва двойно изпращане с предаване (чрез тази ключова дума) на метода за посещение на посетителя.
4. Изпълнение
Нашият пример ще бъде потребителски обект Document, който се състои от JSON и XML конкретни елементи; елементите имат общ абстрактен суперклас, Елементът.
Класът на документа :
public class Document extends Element { List 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 , ще трябва да осигурим изпълнението на този метод.
Въпреки това, поради естеството на шаблона на Visitor, внедряването ще бъде същото, така че в повечето случаи ще се наложи да копираме и поставим кода на шаблона от друг, вече съществуващ елемент:
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); } // ... }
Първо, ние създаваме посетител на елемент , той съдържа алгоритъма, който ще приложим към нашите елементи.
След това настройваме нашия документ с подходящи компоненти и прилагаме посетителя, който ще бъде приет от всеки елемент от обектната структура.
Резултатът ще бъде такъв:
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. Недостатъци
Тъй като всеки модел на проектиране, дори Посетителят има своите недостатъци, особено използването му затруднява поддържането на кода, ако трябва да добавим нови елементи към структурата на обекта.
For example, if we add new YamlElement, then we need to update all existing visitors with the new method desired for processing this element. Following this further, if we have ten or more concrete visitors, that might be cumbersome to update all of them.
Other than this, when using this pattern, the business logic related to one particular object gets spread over all visitor implementations.
7. Conclusion
The Visitor pattern is great to separate the algorithm from the classes on which it operates. Besides that, it makes adding new operation more easily, just by providing a new implementation of the Visitor.
Furthermore, we don't depend on components interfaces, and if they are different, that's fine, since we have a separate algorithm for processing per concrete element.
Нещо повече, Посетителят в крайна сметка може да обобщава данни въз основа на елемента, който преминава.
За да видите по-специализирана версия на шаблона за дизайн на Visitor, разгледайте шаблона за посетители в Java NIO - използването на шаблона в JDK.
Както обикновено, пълният код е достъпен в проекта Github.