Skip to content

Visitor Pattern — Separate Operations from Object Structure

DodaTech Updated 2026-06-24 4 min read

What You'll Learn

You will learn how the Visitor pattern lets you add new operations to a class hierarchy without modifying the classes themselves, by routing calls through a double-dispatch mechanism. You will understand the trade-off between adding new operations (easy) and adding new element types (hard).

Why It Matters

An Abstract Syntax Tree may need operations for Type Checking, optimisation, Code Generation, and pretty printing. Adding each operation to every node class pollutes them with unrelated logic. Visitor separates the operation from the object structure so new operations can be added without touching existing classes. Without Visitor, adding a new operation means modifying every node class — with Visitor, you write one new class.

Real-World Use

Compilers use visitors to traverse ASTs for Semantic Analysis, optimisation passes, and Code Generation. DodaTech's configuration validator uses visitors to walk the config tree and apply different validation rules — required fields, type checks, cross-reference resolution — without coupling validation logic to config node classes. Adding a new validation rule means writing one visitor; the config node classes remain untouched.

The Pattern

Visitor declares visit methods for each element type. Element accepts a visitor. ConcreteVisitor implements operations. ObjectStructure provides iteration over elements.

from abc import ABC, abstractmethod

class Visitor(ABC):
    @abstractmethod
    def visit_circle(self, circle: "Circle") -> str:
        pass

    @abstractmethod
    def visit_rectangle(self, rect: "Rectangle") -> str:
        pass

class Shape(ABC):
    @abstractmethod
    def accept(self, visitor: Visitor) -> str:
        pass

class Circle(Shape):
    def __init__(self, radius: float):
        self.radius = radius

    def accept(self, visitor: Visitor) -> str:
        return visitor.visit_circle(self)

class Rectangle(Shape):
    def __init__(self, width: float, height: float):
        self.width = width
        self.height = height

    def accept(self, visitor: Visitor) -> str:
        return visitor.visit_rectangle(self)

class AreaVisitor(Visitor):
    def visit_circle(self, circle: Circle) -> str:
        area = 3.14159 * circle.radius ** 2
        return f"Circle area: {area:.2f}"

    def visit_rectangle(self, rect: Rectangle) -> str:
        area = rect.width * rect.height
        return f"Rectangle area: {area:.2f}"

class XMLExportVisitor(Visitor):
    def visit_circle(self, circle: Circle) -> str:
        return f'<circle radius="{circle.radius}" />'

    def visit_rectangle(self, rect: Rectangle) -> str:
        return f'<rect width="{rect.width}" height="{rect.height}" />'
shapes = [Circle(5), Rectangle(4, 6)]
area_visitor = AreaVisitor()
xml_visitor = XMLExportVisitor()

for shape in shapes:
    print(shape.accept(area_visitor))

for shape in shapes:
    print(shape.accept(xml_visitor))
Circle area: 78.54
Rectangle area: 24.00
<circle radius="5" />
<rect width="4" height="6" />

Structure

classDiagram
    class Visitor {
        <>
        +visitConcreteElementA(ElementA)
        +visitConcreteElementB(ElementB)
    }
    class ConcreteVisitor1 {
        +visitConcreteElementA(ElementA)
        +visitConcreteElementB(ElementB)
    }
    class ConcreteVisitor2 {
        +visitConcreteElementA(ElementA)
        +visitConcreteElementB(ElementB)
    }
    class Element {
        <>
        +accept(Visitor)
    }
    class ConcreteElementA {
        +accept(Visitor)
        +operationA()
    }
    class ConcreteElementB {
        +accept(Visitor)
        +operationB()
    }
    ConcreteVisitor1 ..|> Visitor
    ConcreteVisitor2 ..|> Visitor
    ConcreteElementA ..|> Element
    ConcreteElementB ..|> Element
    ConcreteElementA --> Visitor : double-dispatch
    ConcreteElementB --> Visitor : double-dispatch

Real-World Usage

  • Java compiler javac — uses visitor pattern for AST traversal in linting and Code Generation.
  • Python ast.NodeVisitor — standard library visitor for walking and transforming Python AST nodes.
  • .NET Roslyn analyzers — walk syntax trees using the visitor pattern to detect code issues.
  • Serialisation frameworks — Jackson, Gson, and other libraries use visitors to traverse object graphs and produce JSON/XML.

Pros and Cons

Pros Cons
New operations added without modifying element classes Adding new element types requires changing all visitors
Related operations grouped in a single class Breaks Encapsulation — visitors may need access to private fields
Double dispatch simplifies complex conditional logic Can be overkill for simple, stable hierarchies
Accumulates state across the traversal Cyclic dependencies between visitor and element packages

The code demonstrates double dispatch. When shape.accept(<a href="/design-patterns/visitor/">visitor</a>) is called, the specific Circle or Rectangle class determines which visit_* method is called on the visitor. This two-step dispatch means the visitor can perform type-specific operations without using isinstance checks. Adding a new operation like PerimeterVisitor requires no changes to Circle or Rectangle.

Practice Questions

  1. Implement a visitor that counts nodes of each type in a tree structure.
  2. How does double dispatch work, and why is it essential for the Visitor pattern?
  3. Compare Visitor with a simple loop over elements with type checks — what maintenance costs does each approach carry?
  4. Implement a visitor that transforms elements (mutates the structure) rather than just reading them.

Challenge

Add a new shape called Triangle to the hierarchy. What changes are required to existing visitors? What does this reveal about the trade-off inherent in the Visitor pattern?

Real-World Task

If your application has a stable class hierarchy but needs frequent new operations, use DodaTech's AST explorer to Prototype a Visitor-based approach and compare the maintenance cost against adding methods to each class.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro