Visitor Pattern
Represents an operation to be performed on elements of an object structure. Visitor lets you define new operations without changing the classes of the elements on which it operates, separating algorithms from the objects they act on.
✦ Problem
You need to perform many distinct, unrelated operations on elements in an object structure (like an AST or composite tree). Adding each operation as a method to every element class pollutes their interfaces and violates the single responsibility principle. The elements change every time you add a new operation.
✦ Solution
Create a visitor interface with a visit method for each element type. Each visitor implementation defines one complete operation across all element types. Elements implement an accept method that calls the appropriate visit method on the visitor. Adding a new operation means creating a new visitor class without modifying any element class.
Participants
| Role | Responsibility |
|---|---|
| Visitor | Declares a visit method for each type of ConcreteElement in the structure |
| ConcreteVisitor | Implements each visit method with the specific operation for that element type |
| Element | Declares an accept method that takes a Visitor as an argument |
| ConcreteElement | Implements accept by calling the correct visit method on the visitor |
TypeScript Example
interface ASTVisitor<T> {
visitNumber(node: NumberNode): T;
visitBinary(node: BinaryNode): T;
visitUnary(node: UnaryNode): T;
}
interface ASTNode {
accept<T>(visitor: ASTVisitor<T>): T;
}
class NumberNode implements ASTNode {
constructor(public value: number) {}
accept<T>(v: ASTVisitor<T>) { return v.visitNumber(this); }
}
class BinaryNode implements ASTNode {
constructor(public op: string, public left: ASTNode, public right: ASTNode) {}
accept<T>(v: ASTVisitor<T>) { return v.visitBinary(this); }
}
class UnaryNode implements ASTNode {
constructor(public op: string, public operand: ASTNode) {}
accept<T>(v: ASTVisitor<T>) { return v.visitUnary(this); }
}
class Evaluator implements ASTVisitor<number> {
visitNumber(n: NumberNode) { return n.value; }
visitBinary(n: BinaryNode) {
const l = n.left.accept(this), r = n.right.accept(this);
switch (n.op) {
case "+": return l + r;
case "*": return l * r;
default: return 0;
}
}
visitUnary(n: UnaryNode) {
return n.op === "-" ? -n.operand.accept(this) : n.operand.accept(this);
}
}
class Printer implements ASTVisitor<string> {
visitNumber(n: NumberNode) { return String(n.value); }
visitBinary(n: BinaryNode) {
return `(${n.left.accept(this)} ${n.op} ${n.right.accept(this)})`;
}
visitUnary(n: UnaryNode) { return `(${n.op}${n.operand.accept(this)})`; }
}
// Expression: -(3 + 4) * 2
const ast = new BinaryNode("*",
new UnaryNode("-", new BinaryNode("+", new NumberNode(3), new NumberNode(4))),
new NumberNode(2),
);
console.log(ast.accept(new Printer())); // ((-(3 + 4)) * 2)
console.log(ast.accept(new Evaluator())); // -14Real-World Example
Compilers use visitors to perform different passes over an Abstract Syntax Tree—type checking, optimization, code generation—each as a separate visitor. Document processors apply visitors for spellchecking, word counting, and formatting. Static analysis tools traverse code ASTs with visitors to find bugs, security issues, or style violations. Babel plugins are visitors that transform JavaScript AST nodes.
✓ When to Use
- •You need to perform many unrelated operations across an object structure
- •The object structure rarely changes but you frequently add new operations
- •You want to gather related operations into a single class instead of spreading them across element classes
- •You work with a composite structure and need to perform operations that depend on concrete element types
✗ When Not to Use
- •The element hierarchy changes frequently (each change requires updating all visitors)
- •There are very few operations and adding a visitor adds unnecessary complexity
- •The elements don't share a common accept interface
Understanding the Visitor Pattern
The Visitor pattern is one of the most important behavioral design patterns in object-oriented software design. Originally cataloged by the Gang of Four (Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides) in their seminal 1994 book "Design Patterns: Elements of Reusable Object-Oriented Software," this pattern continues to be a cornerstone of modern software architecture.
Represents an operation to be performed on elements of an object structure. Visitor lets you define new operations without changing the classes of the elements on which it operates, separating algorithms from the objects they act on. The pattern involves the following key participants: Visitor, ConcreteVisitor, Element, ConcreteElement. Each participant has a well-defined role, and the interactions between them create a flexible, maintainable structure that can evolve with changing requirements.
In TypeScript and modern JavaScript development, the Visitor pattern is particularly valuable because the language's type system and interface support allow you to express the pattern with compile-time safety. TypeScript generics can make Visitor implementations even more flexible and reusable while maintaining strong type checking throughout the pattern's structure.
Knowing when to apply the Visitor pattern is as important as knowing how. Common use cases include: You need to perform many unrelated operations across an object structure; The object structure rarely changes but you frequently add new operations; You want to gather related operations into a single class instead of spreading them across element classes. However, overusing design patterns can lead to unnecessarily complex code. Always evaluate whether the pattern genuinely solves your problem or if a simpler approach would suffice.
The Visitor pattern is related to several other design patterns: Composite, Iterator, Strategy, Command. Understanding these relationships helps you choose the right pattern for your specific situation and combine patterns effectively when building complex systems.
Related Patterns
Composes objects into tree structures to represent part-whole hierarchies. Composite lets clients treat individual objec…
Provides a way to access the elements of an aggregate object sequentially without exposing its underlying representation…
Defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary …
Encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requ…
More Behavioral Patterns
Explore All Design Patterns
Browse our complete reference of 23 design patterns with TypeScript examples, UML participants, and practical guidance.