Skip to content

Visitor Pattern

Category: Behavioral Pattern

Overview

Represent an operation to be performed on elements of an object structure. This pattern lets you define new operations without changing the classes of the elements on which it operates, separating algorithms from the objects they operate on.

Usage Guidelines

Use when:

  • Need to perform many distinct operations on object structure
  • Operations change more frequently than object structure
  • Want to keep related operations together
  • Object structure is stable but operations vary

Avoid when:

  • Object structure changes frequently
  • Only a few operations exist
  • Operations are simple and well-suited as methods
  • Element interfaces change often

Implementation

from __future__ import annotations
from abc import ABC, abstractmethod

class ShapeVisitor(ABC):
    """Abstract visitor for shape operations."""

    @abstractmethod
    def visit_circle(self, circle: Circle) -> str:
        """Visit a circle."""
        pass

    @abstractmethod
    def visit_rectangle(self, rectangle: Rectangle) -> str:
        """Visit a rectangle."""
        pass

class Shape(ABC):
    """Abstract shape that accepts visitors."""

    @abstractmethod
    def accept(self, visitor: ShapeVisitor) -> str:
        """Accept a visitor."""
        pass

class Circle(Shape):
    """Concrete circle shape."""

    def __init__(self, radius: float) -> None:
        """Initialize circle."""
        self.radius = radius

    def accept(self, visitor: ShapeVisitor) -> str:
        """Accept visitor for circle."""
        return visitor.visit_circle(self)

class Rectangle(Shape):
    """Concrete rectangle shape."""

    def __init__(self, width: float, height: float) -> None:
        """Initialize rectangle."""
        self.width = width
        self.height = height

    def accept(self, visitor: ShapeVisitor) -> str:
        """Accept visitor for rectangle."""
        return visitor.visit_rectangle(self)

class AreaCalculator(ShapeVisitor):
    """Visitor that calculates areas of shapes."""

    def visit_circle(self, circle: Circle) -> str:
        """Calculate circle area."""
        import math
        area = math.pi * circle.radius ** 2
        return f"Circle area: {area:.2f}"

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

Usage

# Create shapes
circle = Circle(5.0)
rectangle = Rectangle(4.0, 6.0)

# Calculate areas
area_calc = AreaCalculator()
print(circle.accept(area_calc))  # Circle area: 78.54
print(rectangle.accept(area_calc))  # Rectangle area: 24.00

Trade-offs

Benefits:

  1. Add new operations without modifying elements (Open/Closed Principle)
  2. Groups related operations in visitor classes (Single Responsibility)
  3. Visitor can accumulate state while traversing
  4. Separates algorithms from object structure

Drawbacks:

  1. Visitor may need access to element internals breaking encapsulation
  2. Adding new element types requires updating all visitors
  3. Circular dependencies between element and visitor interfaces
  4. Double dispatch mechanism can be confusing

Real-World Examples

  • Compiler AST operations like type checking and code generation
  • Document processing with rendering, exporting, validating
  • File system operations computing sizes, searching, archiving
  • Graphics rendering for different shapes
  • Composite
  • Iterator
  • Interpreter
  • Strategy

API Reference

design_patterns.behavioral.visitor

Visitor Pattern Module

The Visitor pattern represents an operation to be performed on elements of an object structure. It lets you define new operations without changing the classes of the elements on which it operates. This pattern separates algorithms from the objects they operate on.

Example

Calculating areas and exporting shapes:

circle = Circle(5)
rectangle = Rectangle(4, 6)

area_calculator = AreaCalculator()
print(circle.accept(area_calculator))  # Area: 78.54
print(rectangle.accept(area_calculator))  # Area: 24

exporter = XMLExporter()
print(circle.accept(exporter))  # <Circle radius="5"/>

AreaCalculator

Bases: ShapeVisitor

Visitor that calculates areas of shapes.

Source code in src/design_patterns/behavioral/visitor.py
class AreaCalculator(ShapeVisitor):
    """Visitor that calculates areas of shapes."""

    def visit_circle(self, circle: Circle) -> str:
        """Calculate circle area.

        Args:
            circle: Circle to calculate.

        Returns:
            Area description.
        """
        import math
        area = math.pi * circle.radius ** 2
        return f"Circle area: {area:.2f}"

    def visit_rectangle(self, rectangle: Rectangle) -> str:
        """Calculate rectangle area.

        Args:
            rectangle: Rectangle to calculate.

        Returns:
            Area description.
        """
        area = rectangle.width * rectangle.height
        return f"Rectangle area: {area:.2f}"

    def visit_triangle(self, triangle: Triangle) -> str:
        """Calculate triangle area.

        Args:
            triangle: Triangle to calculate.

        Returns:
            Area description.
        """
        area = (triangle.base * triangle.height) / 2
        return f"Triangle area: {area:.2f}"

visit_circle(circle)

Calculate circle area.

Parameters:

Name Type Description Default
circle Circle

Circle to calculate.

required

Returns:

Type Description
str

Area description.

Source code in src/design_patterns/behavioral/visitor.py
def visit_circle(self, circle: Circle) -> str:
    """Calculate circle area.

    Args:
        circle: Circle to calculate.

    Returns:
        Area description.
    """
    import math
    area = math.pi * circle.radius ** 2
    return f"Circle area: {area:.2f}"

visit_rectangle(rectangle)

Calculate rectangle area.

Parameters:

Name Type Description Default
rectangle Rectangle

Rectangle to calculate.

required

Returns:

Type Description
str

Area description.

Source code in src/design_patterns/behavioral/visitor.py
def visit_rectangle(self, rectangle: Rectangle) -> str:
    """Calculate rectangle area.

    Args:
        rectangle: Rectangle to calculate.

    Returns:
        Area description.
    """
    area = rectangle.width * rectangle.height
    return f"Rectangle area: {area:.2f}"

visit_triangle(triangle)

Calculate triangle area.

Parameters:

Name Type Description Default
triangle Triangle

Triangle to calculate.

required

Returns:

Type Description
str

Area description.

Source code in src/design_patterns/behavioral/visitor.py
def visit_triangle(self, triangle: Triangle) -> str:
    """Calculate triangle area.

    Args:
        triangle: Triangle to calculate.

    Returns:
        Area description.
    """
    area = (triangle.base * triangle.height) / 2
    return f"Triangle area: {area:.2f}"

Circle

Bases: Shape

Concrete circle shape.

Source code in src/design_patterns/behavioral/visitor.py
class Circle(Shape):
    """Concrete circle shape."""

    def __init__(self, radius: float) -> None:
        """Initialize circle.

        Args:
            radius: Circle radius.
        """
        self.radius = radius

    def accept(self, visitor: ShapeVisitor) -> str:
        """Accept visitor for circle.

        Args:
            visitor: Visitor to accept.

        Returns:
            Result of visit.
        """
        return visitor.visit_circle(self)

__init__(radius)

Initialize circle.

Parameters:

Name Type Description Default
radius float

Circle radius.

required
Source code in src/design_patterns/behavioral/visitor.py
def __init__(self, radius: float) -> None:
    """Initialize circle.

    Args:
        radius: Circle radius.
    """
    self.radius = radius

accept(visitor)

Accept visitor for circle.

Parameters:

Name Type Description Default
visitor ShapeVisitor

Visitor to accept.

required

Returns:

Type Description
str

Result of visit.

Source code in src/design_patterns/behavioral/visitor.py
def accept(self, visitor: ShapeVisitor) -> str:
    """Accept visitor for circle.

    Args:
        visitor: Visitor to accept.

    Returns:
        Result of visit.
    """
    return visitor.visit_circle(self)

JSONExporter

Bases: ShapeVisitor

Visitor that exports shapes to JSON format.

Source code in src/design_patterns/behavioral/visitor.py
class JSONExporter(ShapeVisitor):
    """Visitor that exports shapes to JSON format."""

    def visit_circle(self, circle: Circle) -> str:
        """Export circle to JSON.

        Args:
            circle: Circle to export.

        Returns:
            JSON representation.
        """
        return f'{{"type": "circle", "radius": {circle.radius}}}'

    def visit_rectangle(self, rectangle: Rectangle) -> str:
        """Export rectangle to JSON.

        Args:
            rectangle: Rectangle to export.

        Returns:
            JSON representation.
        """
        return f'{{"type": "rectangle", "width": {rectangle.width}, "height": {rectangle.height}}}'

    def visit_triangle(self, triangle: Triangle) -> str:
        """Export triangle to JSON.

        Args:
            triangle: Triangle to export.

        Returns:
            JSON representation.
        """
        return f'{{"type": "triangle", "base": {triangle.base}, "height": {triangle.height}}}'

visit_circle(circle)

Export circle to JSON.

Parameters:

Name Type Description Default
circle Circle

Circle to export.

required

Returns:

Type Description
str

JSON representation.

Source code in src/design_patterns/behavioral/visitor.py
def visit_circle(self, circle: Circle) -> str:
    """Export circle to JSON.

    Args:
        circle: Circle to export.

    Returns:
        JSON representation.
    """
    return f'{{"type": "circle", "radius": {circle.radius}}}'

visit_rectangle(rectangle)

Export rectangle to JSON.

Parameters:

Name Type Description Default
rectangle Rectangle

Rectangle to export.

required

Returns:

Type Description
str

JSON representation.

Source code in src/design_patterns/behavioral/visitor.py
def visit_rectangle(self, rectangle: Rectangle) -> str:
    """Export rectangle to JSON.

    Args:
        rectangle: Rectangle to export.

    Returns:
        JSON representation.
    """
    return f'{{"type": "rectangle", "width": {rectangle.width}, "height": {rectangle.height}}}'

visit_triangle(triangle)

Export triangle to JSON.

Parameters:

Name Type Description Default
triangle Triangle

Triangle to export.

required

Returns:

Type Description
str

JSON representation.

Source code in src/design_patterns/behavioral/visitor.py
def visit_triangle(self, triangle: Triangle) -> str:
    """Export triangle to JSON.

    Args:
        triangle: Triangle to export.

    Returns:
        JSON representation.
    """
    return f'{{"type": "triangle", "base": {triangle.base}, "height": {triangle.height}}}'

PerimeterCalculator

Bases: ShapeVisitor

Visitor that calculates perimeters of shapes.

Source code in src/design_patterns/behavioral/visitor.py
class PerimeterCalculator(ShapeVisitor):
    """Visitor that calculates perimeters of shapes."""

    def visit_circle(self, circle: Circle) -> str:
        """Calculate circle perimeter.

        Args:
            circle: Circle to calculate.

        Returns:
            Perimeter description.
        """
        import math
        perimeter = 2 * math.pi * circle.radius
        return f"Circle perimeter: {perimeter:.2f}"

    def visit_rectangle(self, rectangle: Rectangle) -> str:
        """Calculate rectangle perimeter.

        Args:
            rectangle: Rectangle to calculate.

        Returns:
            Perimeter description.
        """
        perimeter = 2 * (rectangle.width + rectangle.height)
        return f"Rectangle perimeter: {perimeter:.2f}"

    def visit_triangle(self, triangle: Triangle) -> str:
        """Calculate triangle perimeter (assumes right triangle).

        Args:
            triangle: Triangle to calculate.

        Returns:
            Perimeter description.
        """
        import math
        hypotenuse = math.sqrt(triangle.base**2 + triangle.height**2)
        perimeter = triangle.base + triangle.height + hypotenuse
        return f"Triangle perimeter: {perimeter:.2f}"

visit_circle(circle)

Calculate circle perimeter.

Parameters:

Name Type Description Default
circle Circle

Circle to calculate.

required

Returns:

Type Description
str

Perimeter description.

Source code in src/design_patterns/behavioral/visitor.py
def visit_circle(self, circle: Circle) -> str:
    """Calculate circle perimeter.

    Args:
        circle: Circle to calculate.

    Returns:
        Perimeter description.
    """
    import math
    perimeter = 2 * math.pi * circle.radius
    return f"Circle perimeter: {perimeter:.2f}"

visit_rectangle(rectangle)

Calculate rectangle perimeter.

Parameters:

Name Type Description Default
rectangle Rectangle

Rectangle to calculate.

required

Returns:

Type Description
str

Perimeter description.

Source code in src/design_patterns/behavioral/visitor.py
def visit_rectangle(self, rectangle: Rectangle) -> str:
    """Calculate rectangle perimeter.

    Args:
        rectangle: Rectangle to calculate.

    Returns:
        Perimeter description.
    """
    perimeter = 2 * (rectangle.width + rectangle.height)
    return f"Rectangle perimeter: {perimeter:.2f}"

visit_triangle(triangle)

Calculate triangle perimeter (assumes right triangle).

Parameters:

Name Type Description Default
triangle Triangle

Triangle to calculate.

required

Returns:

Type Description
str

Perimeter description.

Source code in src/design_patterns/behavioral/visitor.py
def visit_triangle(self, triangle: Triangle) -> str:
    """Calculate triangle perimeter (assumes right triangle).

    Args:
        triangle: Triangle to calculate.

    Returns:
        Perimeter description.
    """
    import math
    hypotenuse = math.sqrt(triangle.base**2 + triangle.height**2)
    perimeter = triangle.base + triangle.height + hypotenuse
    return f"Triangle perimeter: {perimeter:.2f}"

Rectangle

Bases: Shape

Concrete rectangle shape.

Source code in src/design_patterns/behavioral/visitor.py
class Rectangle(Shape):
    """Concrete rectangle shape."""

    def __init__(self, width: float, height: float) -> None:
        """Initialize rectangle.

        Args:
            width: Rectangle width.
            height: Rectangle height.
        """
        self.width = width
        self.height = height

    def accept(self, visitor: ShapeVisitor) -> str:
        """Accept visitor for rectangle.

        Args:
            visitor: Visitor to accept.

        Returns:
            Result of visit.
        """
        return visitor.visit_rectangle(self)

__init__(width, height)

Initialize rectangle.

Parameters:

Name Type Description Default
width float

Rectangle width.

required
height float

Rectangle height.

required
Source code in src/design_patterns/behavioral/visitor.py
def __init__(self, width: float, height: float) -> None:
    """Initialize rectangle.

    Args:
        width: Rectangle width.
        height: Rectangle height.
    """
    self.width = width
    self.height = height

accept(visitor)

Accept visitor for rectangle.

Parameters:

Name Type Description Default
visitor ShapeVisitor

Visitor to accept.

required

Returns:

Type Description
str

Result of visit.

Source code in src/design_patterns/behavioral/visitor.py
def accept(self, visitor: ShapeVisitor) -> str:
    """Accept visitor for rectangle.

    Args:
        visitor: Visitor to accept.

    Returns:
        Result of visit.
    """
    return visitor.visit_rectangle(self)

Shape

Bases: ABC

Abstract shape that accepts visitors.

Source code in src/design_patterns/behavioral/visitor.py
class Shape(ABC):
    """Abstract shape that accepts visitors."""

    @abstractmethod
    def accept(self, visitor: ShapeVisitor) -> str:
        """Accept a visitor.

        Args:
            visitor: Visitor to accept.

        Returns:
            Result of the visit.
        """
        pass

accept(visitor) abstractmethod

Accept a visitor.

Parameters:

Name Type Description Default
visitor ShapeVisitor

Visitor to accept.

required

Returns:

Type Description
str

Result of the visit.

Source code in src/design_patterns/behavioral/visitor.py
@abstractmethod
def accept(self, visitor: ShapeVisitor) -> str:
    """Accept a visitor.

    Args:
        visitor: Visitor to accept.

    Returns:
        Result of the visit.
    """
    pass

ShapeCollection

Collection of shapes that can be visited.

Source code in src/design_patterns/behavioral/visitor.py
class ShapeCollection:
    """Collection of shapes that can be visited."""

    def __init__(self) -> None:
        """Initialize empty shape collection."""
        self.shapes: list[Shape] = []

    def add_shape(self, shape: Shape) -> None:
        """Add a shape to the collection.

        Args:
            shape: Shape to add.
        """
        self.shapes.append(shape)

    def accept_all(self, visitor: ShapeVisitor) -> list[str]:
        """Apply visitor to all shapes.

        Args:
            visitor: Visitor to apply.

        Returns:
            List of results from visiting each shape.
        """
        return [shape.accept(visitor) for shape in self.shapes]

__init__()

Initialize empty shape collection.

Source code in src/design_patterns/behavioral/visitor.py
def __init__(self) -> None:
    """Initialize empty shape collection."""
    self.shapes: list[Shape] = []

accept_all(visitor)

Apply visitor to all shapes.

Parameters:

Name Type Description Default
visitor ShapeVisitor

Visitor to apply.

required

Returns:

Type Description
list[str]

List of results from visiting each shape.

Source code in src/design_patterns/behavioral/visitor.py
def accept_all(self, visitor: ShapeVisitor) -> list[str]:
    """Apply visitor to all shapes.

    Args:
        visitor: Visitor to apply.

    Returns:
        List of results from visiting each shape.
    """
    return [shape.accept(visitor) for shape in self.shapes]

add_shape(shape)

Add a shape to the collection.

Parameters:

Name Type Description Default
shape Shape

Shape to add.

required
Source code in src/design_patterns/behavioral/visitor.py
def add_shape(self, shape: Shape) -> None:
    """Add a shape to the collection.

    Args:
        shape: Shape to add.
    """
    self.shapes.append(shape)

ShapeVisitor

Bases: ABC

Abstract visitor for shape operations.

Source code in src/design_patterns/behavioral/visitor.py
class ShapeVisitor(ABC):
    """Abstract visitor for shape operations."""

    @abstractmethod
    def visit_circle(self, circle: Circle) -> str:
        """Visit a circle.

        Args:
            circle: Circle to visit.

        Returns:
            Result of visiting the circle.
        """
        pass

    @abstractmethod
    def visit_rectangle(self, rectangle: Rectangle) -> str:
        """Visit a rectangle.

        Args:
            rectangle: Rectangle to visit.

        Returns:
            Result of visiting the rectangle.
        """
        pass

    @abstractmethod
    def visit_triangle(self, triangle: Triangle) -> str:
        """Visit a triangle.

        Args:
            triangle: Triangle to visit.

        Returns:
            Result of visiting the triangle.
        """
        pass

visit_circle(circle) abstractmethod

Visit a circle.

Parameters:

Name Type Description Default
circle Circle

Circle to visit.

required

Returns:

Type Description
str

Result of visiting the circle.

Source code in src/design_patterns/behavioral/visitor.py
@abstractmethod
def visit_circle(self, circle: Circle) -> str:
    """Visit a circle.

    Args:
        circle: Circle to visit.

    Returns:
        Result of visiting the circle.
    """
    pass

visit_rectangle(rectangle) abstractmethod

Visit a rectangle.

Parameters:

Name Type Description Default
rectangle Rectangle

Rectangle to visit.

required

Returns:

Type Description
str

Result of visiting the rectangle.

Source code in src/design_patterns/behavioral/visitor.py
@abstractmethod
def visit_rectangle(self, rectangle: Rectangle) -> str:
    """Visit a rectangle.

    Args:
        rectangle: Rectangle to visit.

    Returns:
        Result of visiting the rectangle.
    """
    pass

visit_triangle(triangle) abstractmethod

Visit a triangle.

Parameters:

Name Type Description Default
triangle Triangle

Triangle to visit.

required

Returns:

Type Description
str

Result of visiting the triangle.

Source code in src/design_patterns/behavioral/visitor.py
@abstractmethod
def visit_triangle(self, triangle: Triangle) -> str:
    """Visit a triangle.

    Args:
        triangle: Triangle to visit.

    Returns:
        Result of visiting the triangle.
    """
    pass

Triangle

Bases: Shape

Concrete triangle shape.

Source code in src/design_patterns/behavioral/visitor.py
class Triangle(Shape):
    """Concrete triangle shape."""

    def __init__(self, base: float, height: float) -> None:
        """Initialize triangle.

        Args:
            base: Triangle base.
            height: Triangle height.
        """
        self.base = base
        self.height = height

    def accept(self, visitor: ShapeVisitor) -> str:
        """Accept visitor for triangle.

        Args:
            visitor: Visitor to accept.

        Returns:
            Result of visit.
        """
        return visitor.visit_triangle(self)

__init__(base, height)

Initialize triangle.

Parameters:

Name Type Description Default
base float

Triangle base.

required
height float

Triangle height.

required
Source code in src/design_patterns/behavioral/visitor.py
def __init__(self, base: float, height: float) -> None:
    """Initialize triangle.

    Args:
        base: Triangle base.
        height: Triangle height.
    """
    self.base = base
    self.height = height

accept(visitor)

Accept visitor for triangle.

Parameters:

Name Type Description Default
visitor ShapeVisitor

Visitor to accept.

required

Returns:

Type Description
str

Result of visit.

Source code in src/design_patterns/behavioral/visitor.py
def accept(self, visitor: ShapeVisitor) -> str:
    """Accept visitor for triangle.

    Args:
        visitor: Visitor to accept.

    Returns:
        Result of visit.
    """
    return visitor.visit_triangle(self)

XMLExporter

Bases: ShapeVisitor

Visitor that exports shapes to XML format.

Source code in src/design_patterns/behavioral/visitor.py
class XMLExporter(ShapeVisitor):
    """Visitor that exports shapes to XML format."""

    def visit_circle(self, circle: Circle) -> str:
        """Export circle to XML.

        Args:
            circle: Circle to export.

        Returns:
            XML representation.
        """
        return f'<Circle radius="{circle.radius}"/>'

    def visit_rectangle(self, rectangle: Rectangle) -> str:
        """Export rectangle to XML.

        Args:
            rectangle: Rectangle to export.

        Returns:
            XML representation.
        """
        return f'<Rectangle width="{rectangle.width}" height="{rectangle.height}"/>'

    def visit_triangle(self, triangle: Triangle) -> str:
        """Export triangle to XML.

        Args:
            triangle: Triangle to export.

        Returns:
            XML representation.
        """
        return f'<Triangle base="{triangle.base}" height="{triangle.height}"/>'

visit_circle(circle)

Export circle to XML.

Parameters:

Name Type Description Default
circle Circle

Circle to export.

required

Returns:

Type Description
str

XML representation.

Source code in src/design_patterns/behavioral/visitor.py
def visit_circle(self, circle: Circle) -> str:
    """Export circle to XML.

    Args:
        circle: Circle to export.

    Returns:
        XML representation.
    """
    return f'<Circle radius="{circle.radius}"/>'

visit_rectangle(rectangle)

Export rectangle to XML.

Parameters:

Name Type Description Default
rectangle Rectangle

Rectangle to export.

required

Returns:

Type Description
str

XML representation.

Source code in src/design_patterns/behavioral/visitor.py
def visit_rectangle(self, rectangle: Rectangle) -> str:
    """Export rectangle to XML.

    Args:
        rectangle: Rectangle to export.

    Returns:
        XML representation.
    """
    return f'<Rectangle width="{rectangle.width}" height="{rectangle.height}"/>'

visit_triangle(triangle)

Export triangle to XML.

Parameters:

Name Type Description Default
triangle Triangle

Triangle to export.

required

Returns:

Type Description
str

XML representation.

Source code in src/design_patterns/behavioral/visitor.py
def visit_triangle(self, triangle: Triangle) -> str:
    """Export triangle to XML.

    Args:
        triangle: Triangle to export.

    Returns:
        XML representation.
    """
    return f'<Triangle base="{triangle.base}" height="{triangle.height}"/>'