The Visitor Design Pattern: Add Operations Without Modifying Classes
Master the Visitor Pattern in Java. Learn how to add new operations to object structures using double dispatch.
Moshiour Rahman
Advertisement
The Problem: Adding Operations to Closed Hierarchies
Imagine you have a document with different elements:
ParagraphImageTable
Now you need to add operations like:
- Export to PDF
- Export to HTML
- Calculate word count
Naive approach: Add methods to each class:
class Paragraph {
void exportPDF() { ... }
void exportHTML() { ... }
void wordCount() { ... }
}
Problems:
- Violates Open/Closed Principle (modifying existing classes)
- Violates Single Responsibility (each class does export + rendering)
- Can’t add operations if you don’t own the classes
The Solution: The Visitor Pattern
The Visitor Pattern lets you add operations to objects without modifying their classes. It uses double dispatch to separate algorithms from object structure.
Real-Life Analogy: Tax Inspector 🧑💼
A tax inspector visits different building types:
- House: Checks property tax
- Shop: Checks business tax + inventory tax
- Bank: Checks corporate tax + compliance
The inspector (visitor) performs different actions based on the building type (element), but buildings don’t need to know how taxes work.
Visualizing the Pattern

Implementation
1. The Element Interface
// Element: Accepts visitors
public interface DocumentElement {
void accept(Visitor visitor);
}
2. Concrete Elements
public class Paragraph implements DocumentElement {
private String text;
public Paragraph(String text) {
this.text = text;
}
public String getText() { return text; }
@Override
public void accept(Visitor visitor) {
visitor.visit(this); // Double dispatch!
}
}
public class Image implements DocumentElement {
private String url;
public Image(String url) {
this.url = url;
}
public String getUrl() { return url; }
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
3. The Visitor Interface
// Visitor: Defines operations
public interface Visitor {
void visit(Paragraph paragraph);
void visit(Image image);
}
4. Concrete Visitors (Operations)
// Export to HTML
public class HTMLExportVisitor implements Visitor {
private StringBuilder html = new StringBuilder();
@Override
public void visit(Paragraph p) {
html.append("<p>").append(p.getText()).append("</p>\n");
}
@Override
public void visit(Image img) {
html.append("<img src=\"").append(img.getUrl()).append("\"/>\n");
}
public String getHTML() { return html.toString(); }
}
// Word count
public class WordCountVisitor implements Visitor {
private int wordCount = 0;
@Override
public void visit(Paragraph p) {
wordCount += p.getText().split("\\s+").length;
}
@Override
public void visit(Image img) {
// Images don't have words
}
public int getCount() { return wordCount; }
}
Usage
List<DocumentElement> document = List.of(
new Paragraph("Hello world"),
new Image("photo.jpg"),
new Paragraph("Design patterns are cool")
);
// Export to HTML
HTMLExportVisitor htmlVisitor = new HTMLExportVisitor();
for (DocumentElement elem : document) {
elem.accept(htmlVisitor);
}
System.out.println(htmlVisitor.getHTML());
// Count words
WordCountVisitor wordCounter = new WordCountVisitor();
for (DocumentElement elem : document) {
elem.accept(wordCounter);
}
System.out.println("Words: " + wordCounter.getCount()); // 6
Double Dispatch Explained
Single dispatch (normal polymorphism):
elem.render(); // Calls Paragraph.render() or Image.render()
Double dispatch (Visitor):
elem.accept(visitor); // 1st dispatch: calls Paragraph.accept()
// Inside Paragraph.accept():
visitor.visit(this); // 2nd dispatch: calls visitor.visit(Paragraph)
This allows the operation to depend on both the element type and visitor type.
In The Wild (Real World Examples)
1. Compiler AST Traversal
Abstract Syntax Trees use Visitor to perform operations like:
- Type checking
- Code generation
- Optimization
2. Java NIO File Visitor
Files.walkFileTree(startPath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
System.out.println("Visiting: " + file);
return FileVisitResult.CONTINUE;
}
});
Cheat Sheet
| Feature | Details |
|---|---|
| Category | Behavioral |
| Problem Solved | Adding operations without modifying classes |
| Key implementation | accept(Visitor v) + v.visit(this) (double dispatch) |
| Pros | Open/Closed (add operations easily), Single Responsibility |
| Cons | Breaking change (adding new element type requires updating all visitors) |
Tips to Remember 🧠
- “Tax Inspector”: Different buildings, one inspector with different procedures.
- “Double Dispatch”: Element calls visitor, visitor calls back to element.
- “When to use”: Adding operations is frequent, adding element types is rare.
Advertisement
Moshiour Rahman
Software Architect & AI Engineer
Enterprise software architect with deep expertise in financial systems, distributed architecture, and AI-powered applications. Building large-scale systems at Fortune 500 companies. Specializing in LLM orchestration, multi-agent systems, and cloud-native solutions. I share battle-tested patterns from real enterprise projects.
Related Articles
The Observer Design Pattern: Don't Call Us, We'll Call You
Master the Observer Pattern in Java. Learn how to implement event-driven architectures and decouple data sources from listeners.
JavaThe Mediator Design Pattern: Centralize Complex Communications
Master the Mediator Pattern in Java. Learn how to reduce coupling by having objects communicate through a mediator instead of directly.
JavaThe Command Design Pattern: Turning Actions into Objects
Master the Command Pattern in Java. Learn how to encapsulate requests as objects to enable Undo/Redo, queuing, and macro commands.
Comments
Comments are powered by GitHub Discussions.
Configure Giscus at giscus.app to enable comments.