Last Updated on September 24, 2023 by KnownSense
In the Visitor pattern, we employ a visitor class to alter the execution algorithm of an element class. This flexibility allows the element’s execution algorithm to adapt as the visitor changes. This pattern falls under the behavior pattern category. In accordance with this pattern, an element object must accept the visitor object, enabling the visitor to manage operations on the element object.
Imagine developing an app that works with a complex geographic information graph, where each node represents entities like cities, industries, or sightseeing areas. Exporting this graph to XML format became a challenge. You couldn’t modify existing node classes, and adding XML export code within them seemed inappropriate. Moreover, anticipating future changes or additional export formats made the situation more complex.
The Visitor pattern suggests creating a separate visitor class to encapsulate the new behavior, rather than integrating it into existing classes. This visitor class defines methods to process different types of nodes. To handle various node classes gracefully, the visitor employs a technique called Double Dispatch, where objects accept a visitor and invoke the appropriate visiting method themselves. This approach ensures extensibility without altering existing node classes, making it easier to add new behaviors.
Benefits
- Separation of Concerns: It separates the algorithm from the objects it operates on, promoting cleaner and more maintainable code.
- Extensibility: New operations can be added without modifying existing code, making it easy to accommodate changes and enhancements.
- Polymorphism: It leverages polymorphism to execute the appropriate visitor method, enhancing flexibility and adaptability.
- Reusability: Visitors can be reused across different objects and hierarchies, reducing code duplication.
- Encapsulation: It encapsulates operations in visitor classes, encapsulating related behavior.
- Open/Closed Principle: It adheres to the open/closed principle, allowing for extension without modification.
UML of Visitor Pattern
Implementation
Let’s create the simplified GeographicInformationApp as discussed above to export the graph to XML format.
Step1: Define the GeographicElement interface with an accept
method.
interface GeographicElement {
void accept(Visitor visitor);
}
Step2: Create concrete classes for geographic elements: City, Industry, and SightSeeing, implementing the GeographicElement interface.
class City implements GeographicElement {
private String name;
public City(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Industry implements GeographicElement {
private String name;
public Industry(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class SightSeeing implements GeographicElement {
private String name;
public SightSeeing(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
Step3: Define the Visitor interface with methods for visiting each concrete element.
interface Visitor {
void visit(City city);
void visit(Industry industry);
void visit(SightSeeing sightSeeing);
}
Step4: Create a concrete visitor class XMLExporter that implements the Visitor interface to export elements to XML.
class XMLExporter implements Visitor {
private StringBuilder xmlResult = new StringBuilder();
@Override
public void visit(City city) {
xmlResult.append("<City>").append(city.getName()).append("</City>").append("\n");
}
@Override
public void visit(Industry industry) {
xmlResult.append("<Industry>").append(industry.getName()).append("</Industry>").append("\n");
}
@Override
public void visit(SightSeeing sightSeeing) {
xmlResult.append("<SightSeeing>").append(sightSeeing.getName()).append("</SightSeeing>").append("\n");
}
public String getXMLResult() {
return "<GeographicInformation>\n" + xmlResult.toString() + "</GeographicInformation>";
}
}
Step5: In main class, use GeographicElement and XMLExporter and print the final XML result.
public class GeographicInformationApp {
public static void main(String[] args) {
List<GeographicElement> elements = new ArrayList<>();
elements.add(new City("New York"));
elements.add(new Industry("Factory A"));
elements.add(new SightSeeing("Central Park"));
XMLExporter xmlExporter = new XMLExporter();
for (GeographicElement element : elements) {
element.accept(xmlExporter);
}
String xmlResult = xmlExporter.getXMLResult();
System.out.println(xmlResult);
}
}
OUTPUT:
<GeographicInformation>
<City>New York</City>
<Industry>Factory A</Industry>
<SightSeeing>Central Park</SightSeeing>
</GeographicInformation>
Conclusion
In conclusion, the Visitor pattern provides a flexible way to extend the behavior of objects without altering their classes, promoting separation of concerns and ease of adding new operations. It enables adaptability in handling various types of elements, making it a powerful design pattern for maintaining clean and extensible code.