Skip to content
DevPedia

Behavioral patternsGuide 27 of 28

Visitor

How to add operations over a structure of objects without modifying their classes each time, and where this approach runs out.

Updated 6 min read

Visitor is a behavioral pattern that separates an algorithm from the objects it operates on, so you can add new operations without modifying those classes.

The problem

AndesShop’s catalog has several kinds of item — PhysicalProduct, DigitalGiftCard, Bundle (a combo of several products) — and over time it needs to apply operations to them that have nothing to do with each other: calculate the total weight for shipping, calculate the applicable tax, generate XML for export to an external marketplace. Adding each new operation directly as a method on PhysicalProduct, DigitalGiftCard, and Bundle fills those classes with methods that have nothing to do with what a product is, only with the various things someone wants to do with it.

The solution

Visitor moves each operation into a separate visitor class, with a different method for each concrete kind of item. Catalog items only need one method, accept(Visitor visitor), which hands control over to the visitor — and the visitor is the one that decides, based on the item’s real type, what to do with it. That accept calling visitor.visit(this) is what makes double dispatch possible: the concrete this picks the right overloaded visit. Adding a new operation (exporting to a new marketplace, say) means creating a new visitor, without touching any catalog class.

The structure of Visitor: a new operation is a new visitor. Adding TaxVisitor doesn't touch any catalog class.

Example in Java

interface CatalogItem {
    void accept(CatalogVisitor visitor);
}

class PhysicalProduct implements CatalogItem {
    double weightKg;
    public void accept(CatalogVisitor visitor) { visitor.visit(this); }
}

class DigitalGiftCard implements CatalogItem {
    public void accept(CatalogVisitor visitor) { visitor.visit(this); }
}

interface CatalogVisitor {
    void visit(PhysicalProduct product);
    void visit(DigitalGiftCard giftCard);
}

// A concrete visitor: calculates the total weight to ship
class ShippingWeightVisitor implements CatalogVisitor {
    private double totalWeight = 0;

    public void visit(PhysicalProduct product) {
        totalWeight += product.weightKg;
    }

    public void visit(DigitalGiftCard giftCard) {
        // weighs nothing — but the visitor decides that, not the GiftCard class
    }

    public double getTotalWeight() {
        return totalWeight;
    }
}
ShippingWeightVisitor weightVisitor = new ShippingWeightVisitor();
for (CatalogItem item : cart.getItems()) {
    item.accept(weightVisitor); // each item delegates to the visitor based on its own type
}
System.out.println(weightVisitor.getTotalWeight());

When to use it

  • When you need to perform distinct, unrelated operations over an already-defined class hierarchy, without cluttering those classes with methods outside their responsibility.
  • When the class hierarchy you operate on is relatively stable, but the operations applied to it keep growing.

When to avoid it

If the class hierarchy changes frequently (new types are added often), Visitor becomes expensive to maintain: every new type forces you to add a visit() method to all the existing visitor classes.

Benefits and drawbacks

BenefitsDrawbacks
Adding a new operation (tax, XML, weight) means creating a visitor, without touching the catalogAdding a new type to the hierarchy forces you to update every existing visitor
The code for one operation stays together, instead of one loose method in each catalog classIt can break encapsulation if the visitor needs access to too much of each type’s internal state

Relationship with other patterns

  • It combines naturally with Composite to apply operations over a whole tree structure.
  • Iterator handles traversing a collection; Visitor handles what to do with each element during that traversal — they usually show up together.

Share this guide

Search by concept, pattern or practice.