Skip to content
Back to skills

Apply Visitor Pattern

ASecurity

Use when you need to perform many distinct operations on a stable object structure — adding new operations without modifying the element classes by separating the operation from the data structure.

  • 4 stars
  • 0 votes
  • 0 copies
  • 2 views
  • Added September 8, 2026
ai-agentspythonrustgojavaswiftnodeapibackendsecuritydocumentation

Works with

  • api

Security analysis

A100/100

Scanned September 8, 2026

npx -y skills add jeffreytse/grimoire-core --skill apply-visitor-pattern --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Apply Visitor Pattern?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Apply Visitor Pattern
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/jeffreytse-apply-visitor-pattern/badge)](https://www.skillsdirectory.com/skills/jeffreytse-apply-visitor-pattern)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: apply-visitor-pattern
description: Use when you need to perform many distinct operations on a stable object structure — adding new operations without modifying the element classes by separating the operation from the data structure.
source: "Gamma, Helm, Johnson, Vlissides, \"Design Patterns: Elements of Reusable Object-Oriented Software\" (1994) pp. 331–344; Java compiler AST visitors; Python ast.NodeVisitor; LLVM IR pass infrastructure; checkstyle/PMD code analysis"
tags: [design-patterns, behavioral, visitor, oop, developer, open-closed, operation-extension, ast]
related: [apply-composite-pattern, apply-iterator-pattern, apply-solid-principles]
---

# Apply Visitor Pattern

Represent an operation to be performed on elements of an object structure — letting you add new operations without changing the element classes.

## Why This Is Best Practice

**Adopted by:** Java compiler's AST visitors (the `javax.lang.model` API uses Visitor
for all annotation processing — in every Java build), Python's `ast.NodeVisitor` (the
foundation of Flake8, mypy, Bandit, and all Python static analysis tools), LLVM's IR
pass infrastructure (every optimization is a Visitor over the IR — the core of Clang,
Rust's compiler backend, and Swift), and PMD/Checkstyle (code analysis tools visited
80M+ downloads).
**Impact:** GoF documents that without Visitor, adding a new operation to an object
structure requires modifying every element class. Python's `ast.NodeVisitor` allows any
tool to add new analysis operations (type checking, linting, security scanning) without
touching the AST node classes. The entire Python static analysis ecosystem builds on
this single design decision.
**Why best:** Adding operations to each class directly — the alternative — violates OCP
and bloats element classes with unrelated operations. Visitor keeps element classes
focused on data representation; operations are concentrated in visitor classes.

Sources: Gamma et al. (1994) pp. 331–344; Python `ast.NodeVisitor` documentation;
LLVM Pass documentation

## Steps

### Step 1: Define the visitor interface with a method for each element type

```python
from abc import ABC, abstractmethod

class ShapeVisitor(ABC):
    @abstractmethod
    def visit_circle(self, circle: "Circle") -> None: ...

    @abstractmethod
    def visit_rectangle(self, rectangle: "Rectangle") -> None: ...

    @abstractmethod
    def visit_triangle(self, triangle: "Triangle") -> None: ...
```

### Step 2: Add `accept()` to element classes — double dispatch

```python
class Shape(ABC):
    @abstractmethod
    def accept(self, visitor: ShapeVisitor) -> None: ...

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

    def accept(self, visitor: ShapeVisitor) -> None:
        visitor.visit_circle(self)   # double dispatch: runtime type of both self and visitor

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

    def accept(self, visitor: ShapeVisitor) -> None:
        visitor.visit_rectangle(self)
```

### Step 3: Implement concrete visitors — each adds one operation across all elements

```python
class AreaCalculator(ShapeVisitor):
    def __init__(self):
        self.total_area = 0.0

    def visit_circle(self, circle: Circle) -> None:
        import math
        self.total_area += math.pi * circle.radius ** 2

    def visit_rectangle(self, rectangle: Rectangle) -> None:
        self.total_area += rectangle.width * rectangle.height

    def visit_triangle(self, triangle: "Triangle") -> None:
        self.total_area += 0.5 * triangle.base * triangle.height

class SVGExporter(ShapeVisitor):
    def __init__(self):
        self.output = []

    def visit_circle(self, circle: Circle) -> None:
        self.output.append(f'<circle r="{circle.radius}"/>')

    def visit_rectangle(self, rectangle: Rectangle) -> None:
        self.output.append(f'<rect width="{rectangle.width}" height="{rectangle.height}"/>')

    def visit_triangle(self, triangle: "Triangle") -> None:
        self.output.append(f'<polygon points="..."/>')
```

### Step 4: Traverse the structure — apply any visitor to any element

```python
shapes: list[Shape] = [Circle(5), Rectangle(3, 4), Circle(2)]

area_calc = AreaCalculator()
for shape in shapes:
    shape.accept(area_calc)
print(f"Total area: {area_calc.total_area:.2f}")

exporter = SVGExporter()
for shape in shapes:
    shape.accept(exporter)
print("\n".join(exporter.output))
```

Adding a new operation (e.g., `JSONExporter`) requires one new Visitor class — no
changes to `Circle`, `Rectangle`, or `Triangle`.

### Step 5: For recursive structures (trees), traverse in the visitor

```python
class PrintVisitor(ast.NodeVisitor):
    def visit_FunctionDef(self, node):
        print(f"Function: {node.name}")
        self.generic_visit(node)   # recurse into children
```

`generic_visit` (Python's built-in) handles recursion, so the visitor focuses on
the operation, not the traversal.

## When NOT to Use

- **When the element class structure changes frequently** — adding a new element type requires updating every existing visitor. Visitor is best when element classes are stable and operations vary.
- **When elements have very different interfaces** — if the visitor's `visit_X` methods can't access what they need through the element's public interface, the visitor forces breaking encapsulation.

## Common Mistakes

**Forgetting `accept()` on a new element class.** If a new element doesn't implement `accept()`, visitors skip it silently. All element classes must implement `accept()`.

**Visitor that builds state from one traversal and uses it in another.** If `AreaCalculator.total_area` is read mid-traversal by another visitor, state is inconsistent. Each traversal should be independent.

**Using Visitor when Iterator suffices.** If all you need is sequential access to elements without type-specific handling, `apply-iterator-pattern` is simpler. Visitor pays off when behavior differs meaningfully per element type.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…