Visitor Pattern
debt(d7/e5/b5/t5)
Closest to 'only careful code review or runtime testing' (d7). The detection_hints indicate automated detection is 'no' and the tool listed is phpstan, which can catch missing interface implementations but cannot reliably detect the misapplication of Visitor (e.g., using it when the hierarchy changes frequently, or using instanceof checks instead). The code pattern — scattered instanceof checks or modifications to every class when adding an operation — requires human review to recognise as a Visitor misuse rather than a compiler or linter error.
Closest to 'touches multiple files / significant refactor in one component' (e5). The quick_fix describes defining accept() in each element class and implementing operations in Visitor classes. Retrofitting Visitor onto an existing hierarchy means touching every class to add accept(), creating a VisitorInterface, and writing visitor implementations — spanning multiple files. Conversely, removing a poorly applied Visitor also requires touching all element classes and consolidating logic.
Closest to 'persistent productivity tax' (b5). The pattern applies to web and cli contexts and affects the entire class hierarchy it targets. Once Visitor is in place, every new element class added to the hierarchy must implement accept() and every existing Visitor must be updated — creating an ongoing maintenance tax across multiple files. It shapes how operations are added to the hierarchy but doesn't define the whole system's architecture.
Closest to 'notable trap (a documented gotcha most devs eventually learn)' (t5). The misconception field directly identifies the trap: developers believe Visitor requires modifying visited classes for each new operation, when in fact only one accept() method per class is needed and new operations are new Visitor implementations. Additionally, common_mistakes highlight confusion with Strategy and misuse when the hierarchy is unstable. These are well-documented gotchas but not catastrophically counterintuitive.
Also Known As
TL;DR
Explanation
The Visitor pattern uses double dispatch: the object calls accept(visitor), which calls visitor.visit(this). This allows adding new operations to a stable class hierarchy by writing new Visitor classes rather than adding methods to each node. Common uses: abstract syntax tree traversal, serialisation to different formats, static analysis tools, and document export. The trade-off: adding a new node type requires updating all existing visitors.
Common Misconception
Why It Matters
Common Mistakes
- Using Visitor when the class hierarchy changes frequently — each new class requires updating all visitors, which is more painful than adding a method to each class.
- Not defining a VisitorInterface — without it, the pattern provides no type safety.
- Confusing Visitor (new operations, stable classes) with Strategy (interchangeable algorithms, single class).
- Implementing Visitor for simple trees where a recursive method on each node is simpler.
Code Examples
// Without visitor — adding export requires modifying every node:
class TextNode {
public function toHtml(): string { /* ... */ }
public function toPdf(): string { /* ... */ } // Must add to every node class
public function toMarkdown(): string { /* ... */ } // Keeps growing
}
// Visitor — add new export format without touching node classes:
interface NodeVisitor {
public function visitText(TextNode $node): string;
public function visitImage(ImageNode $node): string;
}
class TextNode {
public function accept(NodeVisitor $v): string { return $v->visitText($this); }
}
class MarkdownExporter implements NodeVisitor {
public function visitText(TextNode $n): string { return $n->content; }
public function visitImage(ImageNode $n): string { return ""; }
}