Skip to content

Visitor Design Pattern (Behavioral)

Visitor is a behavioral design pattern that lets you separate algorithms from the objects on which they operate.


Imagine your team develops an app that works with geographic information structured as a colossal graph. Each node of the graph represents a complex entity like a city, an industry, or a sightseeing area. You are tasked with implementing an export feature to XML.

Initially, you might consider adding an export method to each node class. However, this risks polluting the node classes with export logic that is unrelated to their primary job of handling geographic data. Furthermore, if the marketing department asks for a different export format later, you would have to change all those classes again, risking bugs in fragile code.

The Visitor pattern suggests that you place the new behavior into a separate class called a visitor, instead of trying to integrate it into existing classes. The original object that was supposed to perform the behavior is now passed to one of the visitor’s methods as an argument.

To handle different node classes, the visitor implements multiple methods with the same name but different argument types (overloading). However, since many languages don’t support dynamic dispatch for arguments, the pattern uses a technique called Double Dispatch. Instead of the client choosing which method to call, the objects themselves β€œaccept” a visitor and call the appropriate visitor method on themselves.

Imagine a seasoned insurance agent who visits various types of buildings to offer insurance.

  • When visiting a residential building, the agent offers medical insurance.
  • When visiting a bank, the agent offers theft insurance.
  • When visiting a coffee shop, the agent offers fire and flood insurance.

The buildings are the objects being visited, and the agent is the Visitor who applies different logic depending on the class of the building they are currently in.


  1. Visitor Interface declares a set of visiting methods that correspond to the classes of the elements.
  2. Concrete Visitors implement several versions of the same behaviors, tailored for different concrete element classes.
  3. Element Interface declares a method for β€œaccepting” visitors.
  4. Concrete Elements must implement the acceptance method, which redirects the call to the visitor’s method corresponding to the current element class.
  5. Client usually represents a collection or a complex structure (like a Composite tree) that the visitor traverses.
classDiagram
    class Visitor {
        <<interface>>
        +visitConcreteElementA(ElementA)
        +visitConcreteElementB(ElementB)
    }
    class ConcreteVisitor {
        +visitConcreteElementA(ElementA)
        +visitConcreteElementB(ElementB)
    }
    class Element {
        <<interface>>
        +accept(v: Visitor)
    }
    class ConcreteElementA {
        +accept(v: Visitor)
        +featureA()
    }
    class ConcreteElementB {
        +accept(v: Visitor)
        +featureB()
    }

    Visitor <|.. ConcreteVisitor
    Element <|.. ConcreteElementA
    Element <|.. ConcreteElementB
    ConcreteElementA ..> Visitor : v.visitConcreteElementA(this)
    ConcreteElementB ..> Visitor : v.visitConcreteElementB(this)

# The Element interface declares an `accept` method.
interface Shape:
method accept(v: Visitor)
# Each concrete element must implement the `accept` method.
class Dot implements Shape:
method accept(v: Visitor):
v.visitDot(this)
class Circle implements Shape:
method accept(v: Visitor):
v.visitCircle(this)
# The Visitor interface declares visiting methods for every
# element class.
interface Visitor:
method visitDot(d: Dot)
method visitCircle(c: Circle)
# Concrete visitors implement the same algorithm for all elements.
class XMLExportVisitor implements Visitor:
method visitDot(d: Dot):
# Export the dot's ID and center coordinates.
method visitCircle(c: Circle):
# Export the circle's ID, center, and radius.

  • Complex Structures: Use the Visitor when you need to perform an operation on all elements of a complex object structure, such as an object tree.
  • Clean Business Logic: Use it to clean up business logic of auxiliary behaviors by moving them into visitor classes.
  • Rarely Changing Elements: Use the pattern when a behavior makes sense only in some classes of a class hierarchy, but not others.
  1. Declare the visitor interface with a set of β€œvisiting” methods, one for each concrete element class.
  2. Declare the element interface and add an abstract accept method that accepts a visitor object.
  3. Implement the accept methods in all concrete element classes.
  4. The element classes should only know about the visitor interface.
  5. For each new behavior that can’t be placed in the element classes, create a new concrete visitor class.

Pros:

  • Open/Closed Principle: You can introduce a new behavior that can work with objects of different classes without changing these classes.
  • Single Responsibility Principle: You can move multiple versions of the same behavior into the same class.
  • A visitor object can accumulate some useful information while working with various objects.

Cons:

  • You need to update all visitors each time a class is added to or removed from the element hierarchy.
  • Visitors might lack the necessary access to the private fields and methods of the elements they are supposed to work with.
  • Visitor can be used to execute an operation over an entire Composite tree.
  • You can use Visitor along with Iterator to traverse a complex data structure and execute some operation over its elements.

// Visitor
public interface IVisitor
{
void Visit(Book book);
void Visit(Fruit fruit);
}
// Element
public interface IItem
{
void Accept(IVisitor visitor);
}
// Concrete Elements
public class Book : IItem
{
public string Title { get; }
public double Price { get; }
public Book(string title, double price)
{
Title = title;
Price = price;
}
public void Accept(IVisitor visitor) => visitor.Visit(this);
}
public class Fruit : IItem
{
public string Name { get; }
public double Weight { get; }
public double PricePerKg { get; }
public Fruit(string name, double weight, double pricePerKg)
{
Name = name;
Weight = weight;
PricePerKg = pricePerKg;
}
public void Accept(IVisitor visitor) => visitor.Visit(this);
}
// Concrete Visitor
public class PriceCalculator : IVisitor
{
public void Visit(Book book)
{
Console.WriteLine($"Book: {book.Title} β†’ {book.Price}€");
}
public void Visit(Fruit fruit)
{
double total = fruit.Weight * fruit.PricePerKg;
Console.WriteLine($"Fruit: {fruit.Name} β†’ {total}€");
}
}
// Usage
class Program
{
static void Main()
{
var items = new List<IItem>
{
new Book("Clean Code", 30),
new Fruit("Apple", 1.2, 3.5)
};
var visitor = new PriceCalculator();
foreach (var item in items)
item.Accept(visitor);
}
}