Assumptions

What is the Visitor Pattern?

The visitor pattern is a behavioral design pattern that simulates double dispatch to define new behavior without changing the class of the object being visited.

<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/VisitorPattern_01.png" alt="visitor pattern UML diagram" style="max-width:95%;"> </div>

Key components:

  • Element: an interface with a method that accepts a visitor type object. this method is usually called accept.
  • Concrete Element: a concrete implementation that calls out to the visitor method, passing a reference to itself along.
  • Visitor: An interface that implements a visit method for each type of element that can be visited by a concrete visitor.
  • Concrete Visitor:

> Double dispatch is a mechanism that sends out a call to a function that is dependent on the two types of objects involved: the type of the object calling the function and the type of the object the function is being called on.

Conceptualization

To visualize the visitor pattern, we'll model the relationship between exhibits at the local museum and the types of guests that view them. For instance, an art critic might inspect a painting much differently than a programmer or historian would. The same is true if a historian was inspecting an ancient finding; an art critic might not think much of it!

<div style="width:100%; margin:auto;text-align:center;"><img src="https://www.devmaking.com/img/topics/designpatterns/VisitorPattern_02.png" alt="museum visitor pattern UML example" style="max-width:95%;"> </div>

Starting with our element interface, the MuseumExhibit, we'll want to give it at least one method, accept. This method consumes a Visitor object. Additionally, we'll have a getName method so our visitor can query information about the exhibit:

interface MuseumExhibit {
   void accept(Visitor v);
   String getName();
 }

With this in place, let's make some implementations:

class Painting implements MuseumExhibit {
   
   String getName() {
     return "Sunday Afternoon";
   }
   
   void accept(Visitor v) {
     v.visit(this);
   }
 }

class Sculpture implements MuseumExhibit {
   
   String getName() {
     return "Contemplating Person";
   }
   
   void accept(Visitor v) {
     v.visit(this);
   }
 }

class Artifact implements MuseumExhibit {
   
   String getName() {
     return "Ancient Tablet";
   }
   
   void accept(Visitor v) {
     v.visit(this);
   }
 }

Taking a closer look at the accept implementations, we see that we pass the concrete elements along to the visitor object. To accompany this in strictly typed languages, we'll need to implement one visit method for each type of object that uses a visitor. Our interface will look like this:

interface Visitor 
   {
       void visit(Painting painting);
       void visit(Sculpture sculpture);
       void visit(Artifact artifact);
   }

If we wanted to add a new exhibit, say, the snack bar, we'd need to implement the method in our visitor interface, or extend out to another interface as to not break code!

Alternative to Interfaces: Abstract Classes

If your visitor is sufficiently large, you might find that not every method needs to get implemented for every visitor type, which can lead to a lot of copy/paste code. An alternative to using an interface for the base visitor is implementing an abstract class. This has an added benefit (which, if you're not careful, can also be a drawback) of allowing new visit methods to be defined without needing to update every implementing class.

   abstract class Visitor
   {
       // Empty method bodies to default to doing nothing. 
       // You can also throw an error here in cases where you know 
       // a particular combination of objects should never visit each other, 
       // or to warn you that you haven't covered a case.
       virtual void visit(Painting painting) {    }
       virtual void visit(Sculpture sculpture) {    }
       virtual void visit(Artifact artifact) {    }
   }

> Take extra care when contemplating this; empty method bodies can lead to silent code defects, which can be especially difficult to debug. Use this with your best judgement!

Finally, we can create the concrete visitors, Historian and ArtCritic:

class ArtCritic implements Visitor {
   void visit(Painting painting) {
     print("The critic inspects " + painting.getName() + " very closely");
   }
   void visit(Sculpture sculpture) {
     print("The critic admires the form of " + sculpture.getName());
   }
   void visit(Artifact artifact) {
     print("The critic isn't sure what to think of the " + artifact.getName());
   }
 }

class Historian implements Visitor {
  void visit(Painting painting) {
     print("The historian enjoys looking at " + painting.getName());
   }
   void visit(Sculpture sculpture) {
     print("The historian is perplexed by " + sculpture.getName());
   }
   void visit(Artifact artifact) {
     print("The historian estimates the age of the " + artifact.getName());
   }
 }

Client Code:

static void main(String[] args) {
   // Creating our exhibits:
   Painting sunday = new Painting();
   Sculpture thinker = new Sculptor();
   Artifact tablet = new Artifact();
   // Creating our visitor objects:
   Visitor critic = new ArtCritic();
   Visitor historian = new Historian();
   // Art critic inspections:
   print("The art critic inspects the exhibits:");
   sunday.accept(critic);
   thinker.accept(critic);
   tablet.accept(critic);
   // Historian inspections:
   print("The historian inspects the exhibits:");
   sunday.accept(historian);
   thinker.accept(historian);
   tablet.accept(historian);
   
   // Done!
 }