From grimoire
Adds new operations to stable object structures without modifying element classes, using the Visitor pattern. Useful for AST analysis, code quality tools, and compiler passes.
How this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:apply-visitor-patternThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Represent an operation to be performed on elements of an object structure — letting you add new operations without changing the element classes.
Represent an operation to be performed on elements of an object structure — letting you add new operations without changing the element classes.
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
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: ...
accept() to element classes — double dispatchclass 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)
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="..."/>')
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.
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.
visit_X methods can't access what they need through the element's public interface, the visitor forces breaking encapsulation.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.
npx claudepluginhub jeffreytse/grimoire --plugin grimoireAdds new operations to object structures without modifying them, using the Visitor pattern with JavaScript examples for AST, file trees, and DOM.
Generates Visitor pattern for PHP 8.4 to perform operations on object structures without modifying element classes. Includes visitor interface, concrete visitors, visitable elements, and unit tests.
Applies the Composite design pattern to compose objects into tree structures, enabling uniform treatment of leaf and container nodes for part-whole hierarchies.