The Visitor Pattern is a behavioral design pattern that lets you separate algorithms from the objects on which they operate. It is the best way to add new operations to existing object structures without modifying those structures.
The Problem
Imagine you have a complex Mobile App structure (the "Elements") consisting of different components: Button, TextField, and Checkbox. You want to implement a "Export to PDF" feature.
Initially, you add an exportToPdf() method to every component class. Later, you want to add an exportToXml() feature. Then a checkAccessibility() feature.
- Your component classes (which should only handle UI logic) are now bloated with export and audit logic.
- Every time you add a new operation, you have to open and modify every single component class, risking breaking the core UI code.
The Solution: The "Guest"
Instead of putting the logic inside the components, you create a Visitor object. The components "accept" the visitor. When a component accepts a visitor, it passes itself to the visitor's method. The visitor then performs the specific operation for that type of component.
Step-by-Step Java Implementation
1. The Visitor Interface
This defines a "visit" method for every type of element in your structure.
// Visitor.java
public interface Visitor {
void visit(Button button);
void visit(TextField textField);
}
2. The Element Interface
This defines an accept method that takes a visitor.
// Component.java
public interface Component {
void accept(Visitor v);
}
3. Concrete Elements
Each element implements accept by calling the visitor method corresponding to its own class. This is called Double Dispatch.
// Button.java
class Button implements Component {
public void accept(Visitor v) {
v.visit(this); // The button tells the visitor: "Work on me!"
}
}
// TextField.java
class TextField implements Component {
public void accept(Visitor v) {
v.visit(this);
}
}
4. Concrete Visitor
The actual logic for the new operation is encapsulated here.
// XmlExportVisitor.java
class XmlExportVisitor implements Visitor {
@Override
public void visit(Button button) {
System.out.println("Exporting Button to XML format...");
}
@Override
public void visit(TextField textField) {
System.out.println("Exporting TextField to XML format...");
}
}
Full Code for Testing
// Save as VisitorTest.java
import java.util.ArrayList;
import java.util.List;
// 1. Visitor Interface
interface ShapeVisitor {
void visit(Circle circle);
void visit(Rectangle rectangle);
}
// 2. Element Interface
interface Shape {
void accept(ShapeVisitor visitor);
}
// 3. Concrete Elements
class Circle implements Shape {
public double radius = 5.0;
public void accept(ShapeVisitor visitor) { visitor.visit(this); }
}
class Rectangle implements Shape {
public double width = 10.0, height = 20.0;
public void accept(ShapeVisitor visitor) { visitor.visit(this); }
}
// 4. Concrete Visitor: Area Calculator
class AreaVisitor implements ShapeVisitor {
@Override
public void visit(Circle c) {
System.out.println("Circle Area: " + (Math.PI * c.radius * c.radius));
}
@Override
public void visit(Rectangle r) {
System.out.println("Rectangle Area: " + (r.width * r.height));
}
}
// 5. Main Test
public class VisitorTest {
public static void main(String[] args) {
List<Shape> shapes = new ArrayList<>();
shapes.add(new Circle());
shapes.add(new Rectangle());
System.out.println("--- Visitor Pattern: Area Calculation ---");
ShapeVisitor areaCalculator = new AreaVisitor();
for (Shape s : shapes) {
s.accept(areaCalculator);
}
}
}
Why use Visitor?
- Clean Code: You follow the Single Responsibility Principle by moving operational logic into Visitor classes.
- Extensibility: You can add a
JsonExportVisitororValidationVisitorwithout touching theCircleorRectangleclasses. - Accumulation: Visitors can store state while traversing a complex structure (e.g., a visitor that calculates the total weight of a machine parts tree).
Real-World Example
- Compilers: When a compiler reads your code, it builds an "Abstract Syntax Tree" (AST). It then uses Visitors to perform different tasks like type checking, code optimization, and machine code generation without changing the tree nodes.
- Document Processing: A visitor could walk through a document tree to count words, extract images, or check for broken links.