Visitor Design Pattern (Behavioral)
Visitor is a behavioral design pattern that lets you separate algorithms from the objects on which they operate.
Problem
Section titled βProblemβ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.
Solution
Section titled βSolutionβ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.
Real-World Analogy
Section titled βReal-World Analogyβ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.
Structure
Section titled βStructureβ- Visitor Interface declares a set of visiting methods that correspond to the classes of the elements.
- Concrete Visitors implement several versions of the same behaviors, tailored for different concrete element classes.
- Element Interface declares a method for βacceptingβ visitors.
- Concrete Elements must implement the acceptance method, which redirects the call to the visitorβs method corresponding to the current element class.
- Client usually represents a collection or a complex structure (like a Composite tree) that the visitor traverses.
Structure (Class Diagram)
Section titled βStructure (Class Diagram)β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)
Pseudocode
Section titled βPseudocodeβ# 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.Applicability
Section titled βApplicabilityβ- 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.
How to Implement
Section titled βHow to Implementβ- Declare the visitor interface with a set of βvisitingβ methods, one for each concrete element class.
- Declare the element interface and add an abstract
acceptmethod that accepts a visitor object. - Implement the
acceptmethods in all concrete element classes. - The element classes should only know about the visitor interface.
- For each new behavior that canβt be placed in the element classes, create a new concrete visitor class.
Pros and Cons
Section titled βPros and Consβ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.
Relations with other Design Patterns
Section titled βRelations with other Design Patternsβ- 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.
Code Examples
Section titled βCode ExamplesβCode Examples (C#)
Section titled βCode Examples (C#)β// Visitorpublic interface IVisitor{ void Visit(Book book); void Visit(Fruit fruit);}
// Elementpublic interface IItem{ void Accept(IVisitor visitor);}
// Concrete Elementspublic 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 Visitorpublic 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}β¬"); }}
// Usageclass 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); }}Code Examples (Python)
Section titled βCode Examples (Python)βfrom __future__ import annotationsfrom abc import ABC, abstractmethodfrom typing import List
class Component(ABC): @abstractmethod def accept(self, visitor: Visitor) -> None: pass
class ConcreteComponentA(Component): def accept(self, visitor: Visitor) -> None: visitor.visit_concrete_component_a(self)
def exclusive_method_a(self) -> str: return "A"
class ConcreteComponentB(Component): def accept(self, visitor: Visitor) -> None: visitor.visit_concrete_component_b(self)
def special_method_b(self) -> str: return "B"
class Visitor(ABC): @abstractmethod def visit_concrete_component_a(self, element: ConcreteComponentA) -> None: pass
@abstractmethod def visit_concrete_component_b(self, element: ConcreteComponentB) -> None: pass
class ConcreteVisitor1(Visitor): def visit_concrete_component_a(self, element: ConcreteComponentA) -> None: print(f"{element.exclusive_method_a()} + ConcreteVisitor1")
def visit_concrete_component_b(self, element: ConcreteComponentB) -> None: print(f"{element.special_method_b()} + ConcreteVisitor1")
class ConcreteVisitor2(Visitor): def visit_concrete_component_a(self, element: ConcreteComponentA) -> None: print(f"{element.exclusive_method_a()} + ConcreteVisitor2")
def visit_concrete_component_b(self, element: ConcreteComponentB) -> None: print(f"{element.special_method_b()} + ConcreteVisitor2")
def client_code(components: List[Component], visitor: Visitor) -> None: for component in components: component.accept(visitor)
if __name__ == "__main__": components = [ConcreteComponentA(), ConcreteComponentB()]
print("The client code works with all visitors via the base Visitor interface:") visitor1 = ConcreteVisitor1() client_code(components, visitor1)
print("\nIt allows the same client code to work with different types of visitors:") visitor2 = ConcreteVisitor2() client_code(components, visitor2)Code Examples (C++)
Section titled βCode Examples (C++)β#include <iostream>#include <string>#include <vector>#include <memory>
// Forward declarationsclass Book;class Fruit;
// Visitorclass IVisitor {public: virtual ~IVisitor() = default; virtual void Visit(Book& book) = 0; virtual void Visit(Fruit& fruit) = 0;};
// Elementclass IItem {public: virtual ~IItem() = default; virtual void Accept(IVisitor& visitor) = 0;};
// Concrete Elementsclass Book : public IItem {public: std::string Title; double Price;
Book(const std::string& title, double price) : Title(title), Price(price) {}
void Accept(IVisitor& visitor) override { visitor.Visit(*this); }};
class Fruit : public IItem {public: std::string Name; double Weight; double PricePerKg;
Fruit(const std::string& name, double weight, double pricePerKg) : Name(name), Weight(weight), PricePerKg(pricePerKg) {}
void Accept(IVisitor& visitor) override { visitor.Visit(*this); }};
// Concrete Visitorclass PriceCalculator : public IVisitor {public: void Visit(Book& book) override { std::cout << "Book: " << book.Title << " β " << book.Price << "β¬" << std::endl; }
void Visit(Fruit& fruit) override { double total = fruit.Weight * fruit.PricePerKg; std::cout << "Fruit: " << fruit.Name << " β " << total << "β¬" << std::endl; }};
// Usageint main() { std::vector<std::unique_ptr<IItem>> items; items.push_back(std::make_unique<Book>("Clean Code", 30)); items.push_back(std::make_unique<Fruit>("Apple", 1.2, 3.5));
PriceCalculator visitor;
for (auto& item : items) { item->Accept(visitor); }
return 0;}Code Examples (Java)
Section titled βCode Examples (Java)β// Visitorpublic interface IVisitor { void visit(Book book); void visit(Fruit fruit);}
// Elementpublic interface IItem { void accept(IVisitor visitor);}
// Concrete Elementspublic class Book implements IItem {
private final String title; private final double price;
public Book(String title, double price) { this.title = title; this.price = price; }
public String getTitle() { return title; }
public double getPrice() { return price; }
@Override public void accept(IVisitor visitor) { visitor.visit(this); }}
public class Fruit implements IItem {
private final String name; private final double weight; private final double pricePerKg;
public Fruit(String name, double weight, double pricePerKg) { this.name = name; this.weight = weight; this.pricePerKg = pricePerKg; }
public String getName() { return name; }
public double getWeight() { return weight; }
public double getPricePerKg() { return pricePerKg; }
@Override public void accept(IVisitor visitor) { visitor.visit(this); }}
// Concrete Visitorpublic class PriceCalculator implements IVisitor {
@Override public void visit(Book book) { System.out.println("Book: " + book.getTitle() + " β " + book.getPrice() + "β¬"); }
@Override public void visit(Fruit fruit) { double total = fruit.getWeight() * fruit.getPricePerKg(); System.out.println("Fruit: " + fruit.getName() + " β " + total + "β¬"); }}
// Usageimport java.util.ArrayList;import java.util.List;
public class Program { public static void main(String[] args) {
List<IItem> items = new ArrayList<>(); items.add(new Book("Clean Code", 30)); items.add(new Fruit("Apple", 1.2, 3.5));
IVisitor visitor = new PriceCalculator();
for (IItem item : items) { item.accept(visitor); } }}Code Examples (JavaScript)
Section titled βCode Examples (JavaScript)β// Visitor (simulated interface)class IVisitor { visitBook(book) { throw new Error("visitBook() must be implemented"); }
visitFruit(fruit) { throw new Error("visitFruit() must be implemented"); }}
// Element (simulated interface)class IItem { accept(visitor) { throw new Error("accept() must be implemented"); }}
// Concrete Elementsclass Book extends IItem { constructor(title, price) { super(); this.title = title; this.price = price; }
accept(visitor) { visitor.visitBook(this); }}
class Fruit extends IItem { constructor(name, weight, pricePerKg) { super(); this.name = name; this.weight = weight; this.pricePerKg = pricePerKg; }
accept(visitor) { visitor.visitFruit(this); }}
// Concrete Visitorclass PriceCalculator extends IVisitor { visitBook(book) { console.log(`Book: ${book.title} β ${book.price}β¬`); }
visitFruit(fruit) { const total = fruit.weight * fruit.pricePerKg; console.log(`Fruit: ${fruit.name} β ${total}β¬`); }}
// Usageconst items = [ new Book("Clean Code", 30), new Fruit("Apple", 1.2, 3.5)];
const visitor = new PriceCalculator();
for (const item of items) { item.accept(visitor);}Code Examples (Visual Basic)
Section titled βCode Examples (Visual Basic)β' ============================' Visitor' ============================Public Interface IVisitor Sub Visit(book As Book) Sub Visit(fruit As Fruit)End Interface
' ============================' Element' ============================Public Interface IItem Sub Accept(visitor As IVisitor)End Interface
' ============================' Concrete Elements' ============================Public Class Book Implements IItem
Public ReadOnly Property Title As String Public ReadOnly Property Price As Double
Public Sub New(title As String, price As Double) Me.Title = title Me.Price = price End Sub
Public Sub Accept(visitor As IVisitor) Implements IItem.Accept visitor.Visit(Me) End SubEnd Class
Public Class Fruit Implements IItem
Public ReadOnly Property Name As String Public ReadOnly Property Weight As Double Public ReadOnly Property PricePerKg As Double
Public Sub New(name As String, weight As Double, pricePerKg As Double) Me.Name = name Me.Weight = weight Me.PricePerKg = pricePerKg End Sub
Public Sub Accept(visitor As IVisitor) Implements IItem.Accept visitor.Visit(Me) End SubEnd Class
' ============================' Concrete Visitor' ============================Public Class PriceCalculator Implements IVisitor
Public Sub Visit(book As Book) Implements IVisitor.Visit Console.WriteLine($"Book: {book.Title} β {book.Price}β¬") End Sub
Public Sub Visit(fruit As Fruit) Implements IVisitor.Visit Dim total As Double = fruit.Weight * fruit.PricePerKg Console.WriteLine($"Fruit: {fruit.Name} β {total}β¬") End SubEnd Class
' ============================' Usage' ============================Module Program Sub Main() Dim items As New List(Of IItem) From { New Book("Clean Code", 30), New Fruit("Apple", 1.2, 3.5) }
Dim visitor As New PriceCalculator()
For Each item In items item.Accept(visitor) Next End SubEnd ModuleCode Examples (Rust)
Section titled βCode Examples (Rust)β// Visitortrait Visitor { fn visit_book(&self, book: &Book); fn visit_fruit(&self, fruit: &Fruit);}
// Elementtrait Item { fn accept(&self, visitor: &dyn Visitor);}
// Concrete Elementsstruct Book { title: String, price: f64,}
impl Book { fn new(title: &str, price: f64) -> Self { Self { title: title.to_string(), price, } }}
impl Item for Book { fn accept(&self, visitor: &dyn Visitor) { visitor.visit_book(self); }}
struct Fruit { name: String, weight: f64, price_per_kg: f64,}
impl Fruit { fn new(name: &str, weight: f64, price_per_kg: f64) -> Self { Self { name: name.to_string(), weight, price_per_kg, } }}
impl Item for Fruit { fn accept(&self, visitor: &dyn Visitor) { visitor.visit_fruit(self); }}
// Concrete Visitorstruct PriceCalculator;
impl Visitor for PriceCalculator { fn visit_book(&self, book: &Book) { println!("Book: {} β {}β¬", book.title, book.price); }
fn visit_fruit(&self, fruit: &Fruit) { let total = fruit.weight * fruit.price_per_kg; println!("Fruit: {} β {}β¬", fruit.name, total); }}
// Usagefn main() { let items: Vec<Box<dyn Item>> = vec![ Box::new(Book::new("Clean Code", 30.0)), Box::new(Fruit::new("Apple", 1.2, 3.5)), ];
let visitor = PriceCalculator;
for item in items { item.accept(&visitor); }}Code Examples (PHP)
Section titled βCode Examples (PHP)β<?php
// Visitorinterface IVisitor{ public function visitBook(Book $book): void; public function visitFruit(Fruit $fruit): void;}
// Elementinterface IItem{ public function accept(IVisitor $visitor): void;}
// Concrete Elementsclass Book implements IItem{ public string $title; public float $price;
public function __construct(string $title, float $price) { $this->title = $title; $this->price = $price; }
public function accept(IVisitor $visitor): void { $visitor->visitBook($this); }}
class Fruit implements IItem{ public string $name; public float $weight; public float $pricePerKg;
public function __construct(string $name, float $weight, float $pricePerKg) { $this->name = $name; $this->weight = $weight; $this->pricePerKg = $pricePerKg; }
public function accept(IVisitor $visitor): void { $visitor->visitFruit($this); }}
// Concrete Visitorclass PriceCalculator implements IVisitor{ public function visitBook(Book $book): void { echo "Book: {$book->title} β {$book->price}β¬\n"; }
public function visitFruit(Fruit $fruit): void { $total = $fruit->weight * $fruit->pricePerKg; echo "Fruit: {$fruit->name} β {$total}β¬\n"; }}
// Usage$items = [ new Book("Clean Code", 30), new Fruit("Apple", 1.2, 3.5)];
$visitor = new PriceCalculator();
foreach ($items as $item) { $item->accept($visitor);}Code Examples (Go)
Section titled βCode Examples (Go)βpackage main
import ( "fmt")
// Visitortype Visitor interface { VisitBook(b *Book) VisitFruit(f *Fruit)}
// Elementtype Item interface { Accept(v Visitor)}
// Concrete Elementstype Book struct { Title string Price float64}
func NewBook(title string, price float64) *Book { return &Book{Title: title, Price: price}}
func (b *Book) Accept(v Visitor) { v.VisitBook(b)}
type Fruit struct { Name string Weight float64 PricePerKg float64}
func NewFruit(name string, weight, pricePerKg float64) *Fruit { return &Fruit{Name: name, Weight: weight, PricePerKg: pricePerKg}}
func (f *Fruit) Accept(v Visitor) { v.VisitFruit(f)}
// Concrete Visitortype PriceCalculator struct{}
func (PriceCalculator) VisitBook(b *Book) { fmt.Printf("Book: %s β %.2fβ¬\n", b.Title, b.Price)}
func (PriceCalculator) VisitFruit(f *Fruit) { total := f.Weight * f.PricePerKg fmt.Printf("Fruit: %s β %.2fβ¬\n", f.Name, total)}
// Usagefunc main() { items := []Item{ NewBook("Clean Code", 30), NewFruit("Apple", 1.2, 3.5), }
visitor := PriceCalculator{}
for _, item := range items { item.Accept(visitor) }}Code Examples (Ruby)
Section titled βCode Examples (Ruby)β# Visitorclass Visitordef visit_book(book) raise NotImplementedError, "Subclasses must implement visit_book"end
def visit_fruit(fruit) raise NotImplementedError, "Subclasses must implement visit_fruit"endend
# Elementclass Itemdef accept(visitor) raise NotImplementedError, "Subclasses must implement accept"endend
# Concrete Elementsclass Book < Itemattr_reader :title, :price
def initialize(title, price) @title = title @price = priceend
def accept(visitor) visitor.visit_book(self)endend
class Fruit < Itemattr_reader :name, :weight, :price_per_kg
def initialize(name, weight, price_per_kg) @name = name @weight = weight @price_per_kg = price_per_kgend
def accept(visitor) visitor.visit_fruit(self)endend
# Concrete Visitorclass PriceCalculator < Visitordef visit_book(book) puts "Book: #{book.title} β #{book.price}β¬"end
def visit_fruit(fruit) total = fruit.weight * fruit.price_per_kg puts "Fruit: #{fruit.name} β #{total}β¬"endend
# Usageitems = [Book.new("Clean Code", 30),Fruit.new("Apple", 1.2, 3.5)]
visitor = PriceCalculator.new
items.each { |item| item.accept(visitor) }Code Examples (Perl)
Section titled βCode Examples (Perl)β#!/usr/bin/perluse strict;use warnings;
############################# Visitor (Interface-like)############################package Visitor;
sub visit_book { die "visit_book() must be implemented by subclass";}
sub visit_fruit { die "visit_fruit() must be implemented by subclass";}
############################# Element (Interface-like)############################package Item;
sub accept { die "accept() must be implemented by subclass";}
############################# Concrete Element: Book############################package Book;our @ISA = ('Item');
sub new { my ($class, $title, $price) = @_; return bless { title => $title, price => $price }, $class;}
sub accept { my ($self, $visitor) = @_; $visitor->visit_book($self);}
sub get_title { $_[0]->{title} }sub get_price { $_[0]->{price} }
############################# Concrete Element: Fruit############################package Fruit;our @ISA = ('Item');
sub new { my ($class, $name, $weight, $price_per_kg) = @_; return bless { name => $name, weight => $weight, price_per_kg => $price_per_kg }, $class;}
sub accept { my ($self, $visitor) = @_; $visitor->visit_fruit($self);}
sub get_name { $_[0]->{name} }sub get_weight { $_[0]->{weight} }sub get_price_per_kg { $_[0]->{price_per_kg} }
############################# Concrete Visitor############################package PriceCalculator;our @ISA = ('Visitor');
sub new { bless {}, shift }
sub visit_book { my ($self, $book) = @_; print "Book: " . $book->get_title() . " β " . $book->get_price() . "β¬\n";}
sub visit_fruit { my ($self, $fruit) = @_; my $total = $fruit->get_weight() * $fruit->get_price_per_kg(); print "Fruit: " . $fruit->get_name() . " β $totalβ¬\n";}
############################# Usage############################package main;
my @items = ( Book->new("Clean Code", 30), Fruit->new("Apple", 1.2, 3.5));
my $visitor = PriceCalculator->new();
foreach my $item (@items) { $item->accept($visitor);}Code Examples (TypeScript)
Section titled βCode Examples (TypeScript)β// Visitorinterface IVisitor { visitBook(book: Book): void; visitFruit(fruit: Fruit): void;}
// Elementinterface IItem { accept(visitor: IVisitor): void;}
// Concrete Elementsclass Book implements IItem { title: string; price: number;
constructor(title: string, price: number) { this.title = title; this.price = price; }
accept(visitor: IVisitor): void { visitor.visitBook(this); }}
class Fruit implements IItem { name: string; weight: number; pricePerKg: number;
constructor(name: string, weight: number, pricePerKg: number) { this.name = name; this.weight = weight; this.pricePerKg = pricePerKg; }
accept(visitor: IVisitor): void { visitor.visitFruit(this); }}
// Concrete Visitorclass PriceCalculator implements IVisitor { visitBook(book: Book): void { console.log(`Book: ${book.title} β ${book.price}β¬`); }
visitFruit(fruit: Fruit): void { const total = fruit.weight * fruit.pricePerKg; console.log(`Fruit: ${fruit.name} β ${total}β¬`); }}
// Usageconst items: IItem[] = [ new Book("Clean Code", 30), new Fruit("Apple", 1.2, 3.5)];
const visitor = new PriceCalculator();
for (const item of items) { item.accept(visitor);}