Introduction
The Visitor Pattern is a behavioral design pattern that allows you to add further operations to objects without modifying their classes. It separates the algorithm from the objects on which it operates, adhering to the Open/Closed Principle.
This pattern is especially useful when working with complex object structures (like ASTs, file systems, document models), where new operations are frequently added but modifying the element classes is undesirable or impossible.
☑️ Core Intent and Participants
Intent: Represent an operation to be performed on the elements of an object structure. The pattern lets you define a new operation without changing the classes of the elements on which it operates.
Participants:
- Visitor Interface: Declares visit methods for each element type.
- Concrete Visitor: Implements operations for each element.
- Element Interface: Declares
accept()
method that takes a visitor. - Concrete Element: Implements
accept()
and calls the visitor's method.
UML Diagram (Textual)
+-------------------+ +-------------------+
| Visitor |<---------| ConcreteVisitor |
+-------------------+ +-------------------+
| +visit(ElementA) | | +visit(ElementA) |
| +visit(ElementB) | | +visit(ElementB) |
+-------------------+ +-------------------+
▲ ▲
| |
+-------------------+ +-------------------+
| Element |<---------| ConcreteElementA |
+-------------------+ +-------------------+
| +accept(Visitor) | | +accept(Visitor) |
+-------------------+ +-------------------+
🚀 Real-World Use Cases
- Compilers & Interpreters: Traverse AST nodes with visitors (e.g., semantic analysis, type checking)
- Document structure renderers: Render text, image, table elements differently
- Code analyzers and linters
- File systems: Perform operations on files and directories
- UI frameworks: Perform actions on component trees
💡 Java Implementation
Step 1: Define the Visitor Interface
public interface Visitor {
void visit(Book book);
void visit(Pen pen);
}
Step 2: Define the Element Interface
public interface Item {
void accept(Visitor visitor);
}
Step 3: Concrete Elements
public class Book implements Item {
private String title;
private double price;
public Book(String title, double price) {
this.title = title;
this.price = price;
}
public String getTitle() { return title; }
public double getPrice() { return price; }
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
public class Pen implements Item {
private String color;
private double price;
public Pen(String color, double price) {
this.color = color;
this.price = price;
}
public String getColor() { return color; }
public double getPrice() { return price; }
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
Step 4: Concrete Visitor
public class BillingVisitor implements Visitor {
private double total = 0;
@Override
public void visit(Book book) {
System.out.println("Book: " + book.getTitle() + " - ₹" + book.getPrice());
total += book.getPrice();
}
@Override
public void visit(Pen pen) {
System.out.println("Pen: " + pen.getColor() + " - ₹" + pen.getPrice());
total += pen.getPrice();
}
public double getTotal() {
return total;
}
}
Step 5: Demo
public class Demo {
public static void main(String[] args) {
Item[] items = new Item[] {
new Book("Design Patterns", 499),
new Pen("Blue", 20),
new Book("Clean Code", 399),
new Pen("Black", 25)
};
BillingVisitor visitor = new BillingVisitor();
for (Item item : items) {
item.accept(visitor);
}
System.out.println("Total Bill: ₹" + visitor.getTotal());
}
}
✅ Pros and Cons
✅ Pros
- Clean separation of operations from data structure
- Easy to add new operations (just add a new Visitor)
- Encourages Open/Closed Principle
- Double dispatch ensures type-safe operation selection
❌ Cons
- Hard to add new elements (need to update all visitors)
- Slightly complex structure with multiple interfaces
- Breaks encapsulation if visitors rely on internal state
🔥 Anti-patterns and Misuse
- Using Visitor Pattern when operations don’t vary or are rare
- Creating visitor classes that do too many unrelated things
- Making element logic dependent on visitor return values (tight coupling)
🔁 Related Patterns Comparison
Pattern | Purpose |
---|---|
Visitor | Separate operations from data structures |
Strategy | Interchangeable algorithms via interfaces |
Command | Encapsulate actions as objects |
Composite | Tree structure with uniform operations |
🟡 Visitor vs Strategy: Visitor operates on object structure elements; Strategy operates independently on data or context.
🔄 Refactoring Legacy Code
If you have a switch/case based on object type (e.g., if (obj instanceof Book)
), replace it with a Visitor to clean up the type-checking logic and distribute behavior.
🧠 Best Practices
- Keep Visitor interfaces focused (Single Responsibility)
- Avoid deep visitor trees—group related logic per visitor type
- Don't overuse—only apply when needed (multiple operations on fixed hierarchy)
- Make Element and Visitor interfaces part of the public API only if necessary
🧩 Real-World Analogy
Tax Officers Visiting Shops
A tax officer (Visitor) can perform different audits on a Bookstore or a Pen Shop (Elements). Each shop accepts the officer and allows them to inspect as needed. If you add another officer (e.g., Environmental Inspector), shops stay the same, but new inspection logic can be added.
🧪 Java Version Relevance
✅ Java 8+
Use lambdas to simplify trivial visitors, but full visitor pattern benefits from interface separation.
✅ Java 17+
Use sealed classes to model finite sets of elements or visitors safely:
public sealed interface Item permits Book, Pen { ... }
📝 Conclusion and Key Takeaways
- Visitor Pattern is ideal when you need to apply multiple operations to a set of related object types.
- It decouples algorithms from the data structure, adhering to the Open/Closed Principle.
- Tradeoff: adding new visitors is easy, but adding new elements is harder.
✅ Use when: Object structure is stable, but operations vary frequently.
🚫 Avoid when: Adding new object types frequently or structure is volatile.
❓ FAQ – Visitor Pattern in Java
1. What is the Visitor Pattern used for?
To define new operations on object structures without changing their classes.
2. What's the core benefit of Visitor Pattern?
It separates the algorithm from the object structure, improving extensibility.
3. Is Visitor Pattern part of Java standard library?
Not directly, but you can find similar structures in XML parsers, compilers, and AST frameworks.
4. What is double dispatch in Visitor Pattern?
It’s a way to choose the correct method based on both the element and the visitor type.
5. When should I avoid Visitor Pattern?
If the object structure changes frequently—it makes maintaining visitors harder.
6. Can Visitor Pattern return values?
Yes, by adjusting method signatures, but it adds complexity.
7. Can I chain multiple visitors?
Yes, especially when doing validations, transformations, and reporting.
8. How is Visitor Pattern related to Composite?
Composite organizes objects hierarchically, Visitor allows performing operations on them.
9. Should elements know about all visitors?
No, they should just call visitor.visit(this)
.
10. Can Visitor work with generics?
Yes, but it adds verbosity. Useful when return types are needed.