🔄 Behavioral

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

RoleResponsibility
VisitorDeclares a visit method for each type of ConcreteElement in the structure
ConcreteVisitorImplements each visit method with the specific operation for that element type
ElementDeclares an accept method that takes a Visitor as an argument
ConcreteElementImplements accept by calling the correct visit method on the visitor

TypeScript Example

TypeScript
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())); // -14

Real-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

More Behavioral Patterns

Explore All Design Patterns

Browse our complete reference of 23 design patterns with TypeScript examples, UML participants, and practical guidance.