Visitor Pattern

Visitor Pattern

Represent an operation to be performed on the elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.

Intent

The Visitor decouples operations from the element classes they operate on. Instead of adding new methods to each element class every time a new operation is needed, you define a Visitor class that implements the operation across all element types. The element classes only need a single accept(visitor) method. New operations are new Visitor classes — no element classes are touched. This isolates unrelated operations from each other and from the element hierarchy.

When NOT to Use

  • The element hierarchy changes frequently — adding a new element type requires changing every existing Visitor interface and every ConcreteVisitor implementation. This violates Open/Closed for elements. If you add new element types more often than new operations, Visitor is the wrong choice — use polymorphism (virtual dispatch) instead
  • Breaking element encapsulation is required to expose enough internal state to the Visitor — if Visitors need access to private internals, the pattern forces poor encapsulation
  • The element hierarchy is small and operations are unlikely to grow — Visitor adds significant structural overhead for minimal gain

When to Use

  • The element hierarchy is stable (rarely gains new types) and operations change or grow frequently
  • Many distinct and unrelated operations must be performed on an object structure without polluting element classes with unrelated methods
  • Accumulating state across multiple elements during traversal is needed (e.g., computing totals, generating reports, collecting statistics)

How It Works

Participants: Visitor (interface declaring visitConcreteElementA(), visitConcreteElementB() — one method per concrete element type), ConcreteVisitor (implements one operation across all element types), Element (interface declaring accept(visitor: Visitor)), ConcreteElement (implements accept() by calling visitor.visit(this)), ObjectStructure (iterates elements and calls accept() on each).

The double-dispatch mechanism: element.accept(visitor) dispatches on the runtime type of element (first dispatch), which then calls visitor.visit(element) dispatching on the runtime type of the visitor (second dispatch). Both the element type and the visitor type are resolved at runtime, ensuring the correct visit method executes for the correct element type.

Class Diagram

classDiagram
    class Visitor {
        <<interface>>
        +visitElementA(e : ElementA)*
        +visitElementB(e : ElementB)*
    }
    class ConcreteVisitor1 {
        +visitElementA(e : ElementA)
        +visitElementB(e : ElementB)
    }
    class ConcreteVisitor2 {
        +visitElementA(e : ElementA)
        +visitElementB(e : ElementB)
    }
    class Element {
        <<interface>>
        +accept(v : Visitor)*
    }
    class ElementA {
        +accept(v : Visitor)
        +operationA()
    }
    class ElementB {
        +accept(v : Visitor)
        +operationB()
    }
    Visitor <|.. ConcreteVisitor1
    Visitor <|.. ConcreteVisitor2
    Element <|.. ElementA
    Element <|.. ElementB
    ElementA --> Visitor : accept calls v.visitElementA(this)
    ElementB --> Visitor : accept calls v.visitElementB(this)

    note for Element "Double dispatch:\nelement.accept(visitor) calls\nvisitor.visitElement(this)"

TypeScript Example

// Visitor — TypeScript
// Source: GoF Design Patterns, p.331
 
interface ShapeVisitor {
  visitCircle(c: Circle): void;
  visitRectangle(r: Rectangle): void;
}
 
interface Shape {
  accept(v: ShapeVisitor): void;
}
 
class Circle implements Shape {
  constructor(public radius: number) {}
  accept(v: ShapeVisitor) { v.visitCircle(this); } // double dispatch
}
 
class Rectangle implements Shape {
  constructor(public width: number, public height: number) {}
  accept(v: ShapeVisitor) { v.visitRectangle(this); } // double dispatch
}
 
class AreaVisitor implements ShapeVisitor {
  visitCircle(c: Circle)         { console.log(Math.PI * c.radius ** 2); }
  visitRectangle(r: Rectangle)   { console.log(r.width * r.height); }
}
 
const shapes: Shape[] = [new Circle(5), new Rectangle(3, 4)];
const areaVisitor = new AreaVisitor();
shapes.forEach(s => s.accept(areaVisitor));

Java Example

// Visitor — Java
// Source: GoF Design Patterns, p.331
 
interface ShapeVisitor {
    void visitCircle(Circle c);
    void visitRectangle(Rectangle r);
}
 
interface Shape {
    void accept(ShapeVisitor v);
}
 
class Circle implements Shape {
    double radius;
    Circle(double r) { this.radius = r; }
    public void accept(ShapeVisitor v) { v.visitCircle(this); }
}
 
class Rectangle implements Shape {
    double width, height;
    Rectangle(double w, double h) { this.width = w; this.height = h; }
    public void accept(ShapeVisitor v) { v.visitRectangle(this); }
}
 
class AreaVisitor implements ShapeVisitor {
    public void visitCircle(Circle c)       { System.out.println(Math.PI * c.radius * c.radius); }
    public void visitRectangle(Rectangle r) { System.out.println(r.width * r.height); }
}

Open/Closed Asymmetry: Visitor makes it easy to add new operations (new ConcreteVisitor class, no element changes). But adding a new element type requires updating every existing Visitor interface and implementation. Use Visitor when the element hierarchy is stable and operations are volatile. Use polymorphism when element types are volatile and operations are stable.

PatternRelationship
Composite-PatternVisitor is typically applied to Composite trees — the ObjectStructure traverses a Composite and calls accept() on each node
Iterator-PatternIterator traverses a structure; Visitor performs an operation during traversal — the two are frequently combined (Iterator provides the traversal, Visitor provides the operation)
Command-PatternCommand encapsulates one request as an object; Visitor encapsulates one operation type across many element types — different scopes of encapsulation

Sources

  • Gamma, Helm, Johnson, Vlissides — Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1994, p.331

Notes that link here: GoF-Patterns-MOC