Prior to Java 5, there was one really common way of implementing the
visitor pattern. I'll use my network simulator as an example,
defining a visitor that can visit any kind of network component
(a card, a cable, a hub or a computer):
public interface Visitor
{
void visit(Card card);
void visit(Hub hub);
void visit(Cable cable);
void visit(Computer computer);
}
and an example accept method:
public void accept(Visitor visitor)
{
visitor.visit(this);
}
For those not initiated with how this works, here's a typical
example:
public static void drawAComponent
(NetworkComponent component,final Graphics graphics)
{
component.accept(new Visitor()
{
public void visit(Card card)
{
graphics.drawRect(10,10,20,20);
}
public void visit(Computer computer)...
});
}
It works quite well, and you can adapt it to make it work over a
collection. In IPSim there is a tree of components (each component
knows its children, and there is a collection of top-level
components). So you can do:
network.visitAll(new Visitor(){etc.});
If we want to inspect the object (get a value back from it) rather
than do something, then we end up with code like this:
public static Dimension getSize(NetworkComponent component)
{
Dimension[] result={null};
component.accept(new Visitor()
{
public void visit(Card card)
{
result[0]=new Dimension(50,60);
}
etc.
});
return result[0];
}
Clearly it would be useful if the visitor could return something
instead of having to set some value. We could make the Visitor's
methods return an int, but then we'd have to write it again if we
later needed another type. There are two solutions to this:
1. Make the methods return Object. This obviously results in
casting, which we all know is for magicians. Let's move on to
Java 5.
2. Make the methods return T, where T is a type parameter on
Visitor. Ok, easy:
public interface Visitor<T>
{
T visit(Card card);
T visit(Cable cable);
T visit(Hub hub);
T visit(Computer computer);
}
and the accept method:
public T accept(Visitor<T> visitor)
{
return visitor.visit(this);
}
The only problem with that is that T is undeclared in the accept
method. Don't make the mistake of declaring it on the class (class
Card<T> implements NetworkComponent<t>), because then you'll only be
able to use one type of Visitor with each instance you create.
Declare it on the method:
public <T> T accept(Visitor<T> visitor)
{
return visitor.visit(this);
}
Now you've got something more useful, the above clunky code becomes:
public static Dimension getSize(NetworkComponent component)
{
return component.accept(new Visitor<Dimension>()
{
public Dimension visit(Card card)
{
return new Dimension(50,60);
}
etc.
});
}
This becomes much more interesting when you apply it across a
collection. Instead of writing code that iterates over all the
components and does something, we can write code that generates a
result. For example, suppose I want to know if any of my components
are a crossover cable. I want to visit all the components, and
return true if any of them are a crossover cable. I could store a
list of booleans, to process later, but that would be wasteful and
indirect. What I want to do is apply the || operator between each
pair of results.
public static boolean areAnyCrossover(Network network)
{
return network.visitAll(new Visitor<Boolean>()
{
public Boolean visit(Cable cable)
{
return cable.isCrossover();
}
public Boolean visit(Computer computer)
{
return false;
}
public Boolean visit(Card card)
{
return false;
}
public Boolean visit(Hub hub)
{
return false;
}
},new Combinator<Boolean,Boolean>()
{
public Boolean combine(Boolean one,Boolean two)
{
return one||two;
}
});
}
Whew. This is an implementation (or at least behind the scenes
there is one) of the 'reduce' algorithm, plus a bit of mapping going
on first. It's very flexible. There's quite a bit of boilerplate
above though, and as the number of component types grows, the amount
of boilerplate grows and grows.
The usual solution is to make an abstract class with empty (return
null;) methods for each visit method, which you subclass to
customise. That works pretty well (remember the @Override
annotation!), but I've been avoiding subclassing for some time now,
so I immediately started to wonder whether there was another way to
do this. I'm not a zealot, if I didn't find another way, I'd just
subclass. I found another way..
public static boolean areAnyCrossover(Network network)
{
return network.visitAll(new DefaultVisitor<Boolean>()
.visitCards(new Function<Card,Boolean>()
{
public Boolean invoke(Card card)
{
return card.isCrossover();
}
})
.visitAnythingElse(new Function<NetworkComponent,
Boolean>()
{
public Boolean invoke(NetworkComponent any)
{
return false;
}
}),new Combinator<Boolean,Boolean>()
{
public Boolean combine
(Boolean one,Boolean two)
{
return one||two;
}
}
});
}
Ok, it doesn't look brilliant, but it does look a little like
functional programming. Let's make these anonymous classes look
more like functions:
public static boolean areAnyCrossover(Network network)
{
return network.visitAll(new DefaultVisitor<Boolean>()
.visitCards({Card card => card.isCrossover()})
.visitAnythingElse(
{NetworkComponent component => false}),
{Boolean one,Boolean two => one||two});
}
One more improvement would be if it were possible to get at || as a
function. Let's imagine that #|| works for this. Let's also
imagine that closures allowed you to elide unused parameters, that
type inference were supported, and that method references were
supported (!):
return network.visitAll(defaultVisitor
.visitCards(Card#isCrossover())
.visitAnythingElse({ => false}),#||);
Interestingly, it still looks like Java, and it doesn't get bloated
whenever you add a type. I don't suppose for an instant that Java 7
will go this far. For an example of the <T> visitor pattern in
current Java, see the source code for javac. It's littered with
them, and it's quite nice to work with, but at least for now,
remember your @Override..
And back to normal Java, suppose you implement Visitor<T>, but then
come across those cases where you don't want to return something.
void doSomething(NetworkComponent component)
{
component.accept(new Visitor<Void>()
{
public Void visit(Card card)
{
whatever.
return null;
}
etc.
};
}
I'd like to be able to use my Visitor type with a <void> type
parameter, but I cannot. Please, if you got this far, and are Neal
Gafter or someone else relevant, consider allowing primitive generic
type parameters. As far as I can see, this can all be done at the
compiler level, making a Visitor<void> appear as a Visitor<Void> in
bytecode. At the moment, I often implement two Visitor types for
each hierarchy - one returning void, and one T. Perhaps that's
overkill, but it reminds me that it's a workaround.