One-line Definition
Separates an algorithm from the object structure it operates on, allowing you to add new behaviors to existing classes without altering them.
Problem
Suppose you have a complex object structure representing a company: Department, Manager, and Employee. You are asked to implement an "Export to XML" feature. You add an exportXML() method to all three classes. Next month, you are asked to add "Export to JSON", then "Calculate Annual Bonus", then "Generate HR Report". Every new feature forces you to modify every class in the hierarchy. This violates the Open/Closed Principle and clutters your domain models with unrelated business logic. The Visitor Pattern solves this by moving the operation out of the classes and into a standalone "Visitor" object.
Real-world Analogy
Think of a supermarket checkout. The scanner (Visitor) doesn't care if it's scanning a Bottle of Milk, a Loaf of Bread, or an Apple (Elements). The checkout system asks each item to "accept" the scanner. The item allows the scanner to read its barcode, and the scanner decides how to calculate the price. The items themselves don't know how to print receipts or calculate taxes; the scanner handles it.
Structure
- Client
- Creates the Visitor and passes it to the elements (usually navigating an Object Structure like a List or a Composite Tree).
- Visitor
- Interface declaring a
visit(ConcreteElement)method for every type of element in the structure.
- Interface declaring a
- ConcreteVisitor
- Implements the Visitor interface to execute a specific algorithm (e.g.,
XMLExportVisitor,TaxCalculationVisitor).
- Implements the Visitor interface to execute a specific algorithm (e.g.,
- Element
- Interface declaring an
accept(Visitor)method.
- Interface declaring an
- ConcreteElement
- Implements the
accept()method, which simply calls the appropriatevisit()method back on the Visitor (Double Dispatch).
- Implements the
Diagram
classDiagram
class Client {
}
class Visitor {
<<interface>>
+visitElementA(ElementA)
+visitElementB(ElementB)
}
class ConcreteVisitor {
+visitElementA(ElementA)
+visitElementB(ElementB)
}
class Element {
<<interface>>
+accept(Visitor)
}
class ConcreteElementA {
+accept(Visitor)
}
class ConcreteElementB {
+accept(Visitor)
}
Client --> Visitor
Client --> Element
ConcreteVisitor ..|> Visitor
ConcreteElementA ..|> Element
ConcreteElementB ..|> Element
ConcreteElementA --> Visitor : calls visitElementA()
Code Walkthrough
Notice how the domain classes (Laptop, Mobile) only have one method related to the visitor: accept(). All the actual pricing logic is neatly extracted into the PostageVisitor. This is called Double Dispatch.
// 1. The Element Interface
interface ItemElement {
void accept(ShoppingCartVisitor visitor);
}
// 2. Concrete Elements (Domain Models)
class Laptop implements ItemElement {
private final int price;
private final int weightGrams;
public Laptop(int price, int weightGrams) {
this.price = price;
this.weightGrams = weightGrams;
}
public int getPrice() { return price; }
public int getWeightGrams() { return weightGrams; }
@Override
public void accept(ShoppingCartVisitor visitor) {
// Double Dispatch: The element passes ITSELF back to the visitor
visitor.visit(this);
}
}
class Mobile implements ItemElement {
private final int price;
private final String brand;
public Mobile(int price, String brand) {
this.price = price;
this.brand = brand;
}
public int getPrice() { return price; }
public String getBrand() { return brand; }
@Override
public void accept(ShoppingCartVisitor visitor) {
visitor.visit(this);
}
}
// 3. The Visitor Interface
interface ShoppingCartVisitor {
void visit(Laptop laptop);
void visit(Mobile mobile);
}
// 4. Concrete Visitor (The Algorithm)
class PostageVisitor implements ShoppingCartVisitor {
private double totalPostage = 0;
@Override
public void visit(Laptop laptop) {
// Laptops cost ₹50 per KG to ship
double postage = (laptop.getWeightGrams() / 1000.0) * 50;
System.out.println("Laptop postage: ₹" + postage);
totalPostage += postage;
}
@Override
public void visit(Mobile mobile) {
// Mobiles have a flat ₹100 shipping fee, except Apple which is free
double postage = mobile.getBrand().equalsIgnoreCase("Apple") ? 0 : 100;
System.out.println(mobile.getBrand() + " Mobile postage: ₹" + postage);
totalPostage += postage;
}
public double getTotalPostage() { return totalPostage; }
}
class Main {
public static void main(String[] args) {
List<ItemElement> cart = Arrays.asList(
new Laptop(50000, 2500),
new Mobile(80000, "Apple"),
new Mobile(15000, "Samsung")
);
PostageVisitor calculator = new PostageVisitor();
for (ItemElement item : cart) {
item.accept(calculator); // Client triggers the visitor
}
System.out.println("Total Postage Cost: ₹" + calculator.getTotalPostage());
}
}
Bad vs Good
Bad Approach
Problems
- The domain models are cluttered with unrelated business logic (
exportXML,calculatePostage). - Adding a new operation requires modifying every single class in the hierarchy.
interface Item {
void exportXML();
void calculatePostage();
}
class Laptop implements Item {
public void exportXML() { /* XML logic */ }
public void calculatePostage() { /* Postage logic */ }
}
// Imagine adding 10 more operations...
Better Approach
Improvements
- Domain models are completely isolated and clean. They just
accepta generic visitor. - Adding a new operation (e.g.,
TaxVisitor) requires zero modifications to the existing classes.
// Domain model stays clean forever
public void accept(Visitor v) { v.visit(this); }
// New features are simply new Visitor classes
Visitor taxCalc = new TaxVisitor();
item.accept(taxCalc);
Pros vs Cons
| Pros | Cons |
|---|---|
| Adding a new operation to the entire class hierarchy is trivial | Adding a new Element to the hierarchy is extremely painful |
| Keeps domain classes hyper-focused and clean (Single Responsibility) | The Visitor interface must know about every single Element class |
| Accumulates state across a complex object structure easily | Violates Encapsulation (Visitor might need public access to Element fields) |
| Uses Double-Dispatch to solve Java's lack of runtime type resolution |
When to Use
- You need to perform various, unrelated operations across a complex object structure (like a Composite tree).
- The class hierarchy of your elements is highly stable and rarely changes, but you frequently need to add new operations/algorithms.
- You want to clean up your domain models by extracting formatting, exporting, or calculating logic into separate classes.
When Not to Use
- The Element class hierarchy changes frequently. (Adding a
Tabletclass requires updating theVisitorinterface and everyConcreteVisitor!). - You can't expose the internal state of your elements through public getters, which the Visitor requires to do its job.
Real-world Examples
java.nio.file.FileVisitor(Used byFiles.walkFileTreeto execute logic on every file/directory visited).javax.lang.model.element.ElementVisitor(Java Annotation Processing API).- Abstract Syntax Tree (AST) processing in compilers.
Key Takeaway
The Visitor Pattern uses a technique called Double Dispatch to cleanly separate algorithms from the data structures they operate on. Use it when your object structure (the Elements) is practically frozen, but you expect to add many new operations (the Visitors) over time. If your object structure is constantly adding new classes, avoid Visitor entirely, as the maintenance burden will be catastrophic.