214
behavioral GOF

Visitor

Reference Wikipedia ↗
Visitor — class diagram
Plate 214 class diagram

The Visitor pattern allows you to add new operations to a hierarchy of objects without modifying the objects themselves. This is achieved by encapsulating the operations in separate “visitor” classes, which can then traverse the object hierarchy and apply their specific logic to each element. It promotes the Open/Closed Principle by allowing extension without modification of the core data structures.

Usage

The Visitor pattern is frequently used when:

  • You need to perform many different, unrelated operations on a complex object structure.
  • The object structure is relatively stable, but the operations you need to perform are likely to change.
  • You want to avoid “bloating” the element classes with numerous operation methods.
  • You need to group related operations together.

Examples

  1. Compilers: In a compiler, a visitor can be used to perform different passes over the abstract syntax tree (AST). For example, one visitor might check for type errors, another might generate intermediate code, and a third might optimize the code. The AST nodes themselves (e.g., Expression, Statement, Identifier) remain unchanged when new analysis or code generation phases are added.

  2. XML Processing: Libraries that process XML or similar document structures often use the Visitor pattern. A visitor can represent an action to be performed on each node type in the document (e.g., printing the node’s value, validating its attributes, transforming it to a different format). The XML node classes (e.g., Element, Attribute, Text) aren’t modified when a new processing rule is introduced. The visitor’s visit() methods handle the specifics of each node type.

Specimens

15 implementations
Specimen 214.01 Dart View specimen ↗

The Visitor pattern allows you to define a new operation without modifying the classes of the objects on which it operates. It’s achieved by moving the operation to a separate “visitor” class that has a visit method for each type of object it can handle. This promotes the Open/Closed Principle.

This Dart implementation defines a basic shape hierarchy (Circle, Square) and a Shape interface. The Visitor interface declares a visit method for each Shape type. ConcreteVisitor implements the Visitor to perform specific operations (e.g., calculating area, printing details). The accept method in each Shape takes a Visitor and calls the appropriate visit method on it, enabling the visitor to operate on the shape. This is idiomatic Dart due to its use of interfaces and method overriding, aligning with its object-oriented nature.

// Define the element interface
abstract class Shape {
  void accept(Visitor visitor);
}

// Concrete elements
class Circle implements Shape {
  final double radius;

  Circle(this.radius);

  @override
  void accept(Visitor visitor) {
    visitor.visitCircle(this);
  }
}

class Square implements Shape {
  final double side;

  Square(this.side);

  @override
  void accept(Visitor visitor) {
    visitor.visitSquare(this);
  }
}

// Define the visitor interface
abstract class Visitor {
  void visitCircle(Circle circle);
  void visitSquare(Square square);
}

// Concrete visitor: Area calculation
class AreaVisitor implements Visitor {
  double totalArea = 0;

  @override
  void visitCircle(Circle circle) {
    totalArea += 3.14159 * circle.radius * circle.radius;
  }

  @override
  void visitSquare(Square square) {
    totalArea += square.side * square.side;
  }
}

// Concrete visitor: Printing details
class PrintVisitor implements Visitor {
  @override
  void visitCircle(Circle circle) {
    print('Circle with radius: ${circle.radius}');
  }

  @override
  void visitSquare(Square square) {
    print('Square with side: ${square.side}');
  }
}

void main() {
  final circle = Circle(5.0);
  final square = Square(4.0);

  // Calculate area
  final areaVisitor = AreaVisitor();
  circle.accept(areaVisitor);
  square.accept(areaVisitor);
  print('Total area: ${areaVisitor.totalArea}');

  // Print details
  final printVisitor = PrintVisitor();
  circle.accept(printVisitor);
  square.accept(printVisitor);
}