The “visitor” pattern | CodeGuru

The “visitor” pattern

Bruce Eckel’s Thinking in Java Contents | Prev | Next Now consider applying a design pattern with an entirely different goal to the trash-sorting problem. For this pattern, we are no longer concerned with optimizing the addition of new types of Trash to the system. Indeed, this pattern makes adding a new type of Trash […]

Written By
CodeGuru Staff
CodeGuru Staff
Mar 1, 2001
4 minute read
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

Now


consider applying a design pattern with an entirely different goal to the


trash-sorting problem.

For


this pattern, we are no longer concerned with optimizing the addition of new


types of


Trash

to


the system. Indeed, this pattern makes adding a new type of


Trash
more

complicated. The assumption is that you have a primary class hierarchy that is


fixed; perhaps it’s from another vendor and you can’t make changes


to that hierarchy. However, you’d like to add new polymorphic methods to


that hierarchy, which means that normally you’d have to add something to


the base class interface. So the dilemma is that you need to add methods to the


base class, but you can’t touch the base class. How do you get around this?

The


design pattern that solves this kind of problem is called a


“visitor” (the final one in the


Design
Patterns

book), and it builds on the double



dispatching


scheme shown in the last section.

The


visitor
pattern allows you to extend the interface of the primary type by creating a
separate class hierarchy of type
Visitor
to
virtualize the operations performed upon the primary type. The objects of the
primary type simply “accept” the visitor, then call the
visitor’s dynamically
bound
method. It looks like this:

Now,


if


v

is a


Visitable

handle


to an


Aluminum

object, the code:

PriceVisitor
pv = new PriceVisitor();

v.accept(pv);

causes


two polymorphic method calls: the first one to select


Aluminum

’s


version of


accept( )

,


and the second one within


accept( )

when the specific version of


visit( )

is called dynamically using the base-class


Visitor

handle


v

.

This


configuration means that new functionality can be added to the system in the


form of new subclasses of


Visitor

.


The


Trash

hierarchy


doesn’t need to be touched. This is the prime benefit of the visitor


pattern: you can add new polymorphic functionality to a class hierarchy without


touching that hierarchy (once the


accept( )

methods have been installed). Note that the benefit is helpful here but not


exactly what we started out to accomplish, so at first blush you might decide


that this isn’t the desired solution.

But


look at one thing that’s been accomplished: the visitor solution avoids


sorting from the master


Trash

sequence into individual typed sequences. Thus, you can leave everything in the


single master sequence and simply pass through that sequence using the


appropriate visitor to accomplish the goal. Although this behavior seems to be


a side effect of visitor, it does give us what we want (avoiding RTTI).

The


double
dispatching in the visitor pattern takes care of determining both the type of
Trash
and
the type of
Visitor.
In
the following example, there are two implementations of
Visitor:
PriceVisitor
to both determine and sum the price, and
WeightVisitor
to keep track of the weights.

You


can see all of this implemented in the new, improved version of the recycling


program. As with


DoubleDispatch.java

,


the


Trash

class is left alone and a new interface is created to add the


accept( )

method:

//: Visitable.java
// An interface to add visitor functionality to 
// the Trash hierarchy without modifying the 
// base class.
package c16.trashvisitor;
import c16.trash.*;
 
interface Visitable {
  // The new method:
  void accept(Visitor v);
} ///:~ 

The


subtypes of


Aluminum

,


Paper

,


Glass,

and


Cardboard

implement the


accept( )

method:

//: VAluminum.java
// Aluminum for the visitor pattern
package c16.trashvisitor;
import c16.trash.*;
 
public class VAluminum extends Aluminum
    implements Visitable {
  public VAluminum(double wt) { super(wt); }
  public void accept(Visitor v) {
    v.visit(this);
  }
} ///:~ 
//: VPaper.java
// Paper for the visitor pattern
package c16.trashvisitor;
import c16.trash.*;
 
public class VPaper extends Paper
    implements Visitable {
  public VPaper(double wt) { super(wt); }
  public void accept(Visitor v) {
    v.visit(this);
  }
} ///:~ 
//: VGlass.java
// Glass for the visitor pattern
package c16.trashvisitor;
import c16.trash.*;
 
public class VGlass extends Glass
    implements Visitable {
  public VGlass(double wt) { super(wt); }
  public void accept(Visitor v) {
    v.visit(this);
  }
} ///:~ 
//: VCardboard.java
// Cardboard for the visitor pattern
package c16.trashvisitor;
import c16.trash.*;
 
public class VCardboard extends Cardboard
    implements Visitable {
  public VCardboard(double wt) { super(wt); }
  public void accept(Visitor v) {
    v.visit(this);
  }
} ///:~ 

Since


there’s nothing concrete in the


Visitor

base class, it can be created as an


interface

:

//: Visitor.java
// The base interface for visitors
package c16.trashvisitor;
import c16.trash.*;
 
interface Visitor {
  void visit(VAluminum a);
  void visit(VPaper p);
  void visit(VGlass g);
  void visit(VCardboard c);
} ///:~ 

Once


again custom


Trash

types have been created in a different subdirectory. The new


Trash

data file is


VTrash.dat

and looks like this:

c16.TrashVisitor.VGlass:54
c16.TrashVisitor.VPaper:22
c16.TrashVisitor.VPaper:11
c16.TrashVisitor.VGlass:17
c16.TrashVisitor.VAluminum:89
c16.TrashVisitor.VPaper:88
c16.TrashVisitor.VAluminum:76
c16.TrashVisitor.VCardboard:96
c16.TrashVisitor.VAluminum:25
c16.TrashVisitor.VAluminum:34
c16.TrashVisitor.VGlass:11
c16.TrashVisitor.VGlass:68
c16.TrashVisitor.VGlass:43
c16.TrashVisitor.VAluminum:27
c16.TrashVisitor.VCardboard:44
c16.TrashVisitor.VAluminum:18
c16.TrashVisitor.VPaper:91
c16.TrashVisitor.VGlass:63
c16.TrashVisitor.VGlass:50
c16.TrashVisitor.VGlass:80
c16.TrashVisitor.VAluminum:81
c16.TrashVisitor.VCardboard:12
c16.TrashVisitor.VGlass:12
c16.TrashVisitor.VGlass:54
c16.TrashVisitor.VAluminum:36
c16.TrashVisitor.VAluminum:93
c16.TrashVisitor.VGlass:93
c16.TrashVisitor.VPaper:80
c16.TrashVisitor.VGlass:36
c16.TrashVisitor.VGlass:12
c16.TrashVisitor.VGlass:60
c16.TrashVisitor.VPaper:66
c16.TrashVisitor.VAluminum:36
c16.TrashVisitor.VCardboard:22

The


rest of the program creates specific


Visitor

types and sends them through a single list of


Trash

objects:

//: TrashVisitor.java 
// The "visitor" pattern
package c16.trashvisitor;
import c16.trash.*;
import java.util.*;
 
// Specific group of algorithms packaged
// in each implementation of Visitor:
class PriceVisitor implements Visitor {
  private double alSum; // Aluminum
  private double pSum; // Paper
  private double gSum; // Glass
  private double cSum; // Cardboard
  public void visit(VAluminum al) {
    double v = al.weight() * al.value();
    System.out.println(
      "value of Aluminum= " + v);
    alSum += v;
  }
  public void visit(VPaper p) {
    double v = p.weight() * p.value();
    System.out.println(
      "value of Paper= " + v);
    pSum += v;
  }
  public void visit(VGlass g) {
    double v = g.weight() * g.value();
    System.out.println(
      "value of Glass= " + v);
    gSum += v;
  }
  public void visit(VCardboard c) {
    double v = c.weight() * c.value();
    System.out.println(
      "value of Cardboard = " + v);
    cSum += v;
  }
  void total() {
    System.out.println(
      "Total Aluminum: $" + alSum + "n" +
      "Total Paper: $" + pSum + "n" +
      "Total Glass: $" + gSum + "n" +
      "Total Cardboard: $" + cSum);
  }
}
 
class WeightVisitor implements Visitor {
  private double alSum; // Aluminum
  private double pSum; // Paper
  private double gSum; // Glass
  private double cSum; // Cardboard
  public void visit(VAluminum al) {
    alSum += al.weight();
    System.out.println("weight of Aluminum = "
        + al.weight());
  }
  public void visit(VPaper p) {
    pSum += p.weight();
    System.out.println("weight of Paper = "
        + p.weight());
  }
  public void visit(VGlass g) {
    gSum += g.weight();
    System.out.println("weight of Glass = "
        + g.weight());
  }
  public void visit(VCardboard c) {
    cSum += c.weight();
    System.out.println("weight of Cardboard = "
        + c.weight());
  }
  void total() {
    System.out.println("Total weight Aluminum:"
        + alSum);
    System.out.println("Total weight Paper:"
        + pSum);
    System.out.println("Total weight Glass:"
        + gSum);
    System.out.println("Total weight Cardboard:"
        + cSum);
  }
}
 
public class TrashVisitor {
  public static void main(String[] args) {
    Vector bin = new Vector();
    // ParseTrash still works, without changes:
    ParseTrash.fillBin("VTrash.dat", bin);
    // You could even iterate through
    // a list of visitors!
    PriceVisitor pv = new PriceVisitor();
    WeightVisitor wv = new WeightVisitor();
    Enumeration it = bin.elements();
    while(it.hasMoreElements()) {
      Visitable v = (Visitable)it.nextElement();
      v.accept(pv);
      v.accept(wv);
    }
    pv.total();
    wv.total();
  }
} ///:~ 

Note


that the shape of


main( )

has changed again. Now there’s only a single


Trash

bin. The two


Visitor

objects are accepted into every element in the sequence, and they perform their


operations. The visitors keep their own internal data to tally the total


weights and prices.

Finally,


there’s no run-time type identification other than the inevitable cast to


Trash

when pulling things out of the sequence. This, too, could be eliminated with


the implementation of parameterized types in Java.

One


way you can distinguish this solution from the double dispatching solution


described previously is to note that, in the double dispatching solution, only


one of the overloaded methods,


add( )

,


was overridden when each subclass was created, while here


each

one of the overloaded


visit( )

methods is overridden in every subclass of


Visitor

.

More
coupling?

There’s


a lot more code here, and there’s definite coupling between the


Trash

hierarchy and the


Visitor

hierarchy. However, there’s also high cohesion within the respective sets


of classes: they each do only one thing (


Trash

describes


Trash, while


Visitor

describes


actions performed on


Trash

),


which is an indicator of a good design. Of course, in this case it works well


only if you’re adding new


Visitor

s,


but it gets in the way when you add new types of


Trash

.

Low


coupling between classes and high cohesion within a class is definitely an


important design goal. Applied mindlessly, though, it can prevent you from


achieving a more elegant design. It seems that some classes inevitably have a


certain intimacy with each other. These often occur in pairs that could perhaps


be called

couplets,
for example, collections and iterators (
Enumerations).
The
Trash-Visitor
pair above appears to be another such couplet.
Contents

|

Prev

|

Next
CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2026 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.