Java's method dispatch algorithm leads to frequent use of the Visitor pattern in Java applications. The most obvious implementation of the pattern is somewhat limiting and difficult to maintain; especially when using static types. You set your type hierarchy when you create the Visitor interface; implying you know this beforehand. That's not always possible - requirements change so adding and removing types as the project evolves is necessary. This process needs to as painless as possible. Because of this I decided to implement the Visitor pattern using the Reflection API.
The goal was to make the code more flexible as I added types to be visited. As it turns out, just adding reflective dispatch is helpful but you can get a more robust system by altering the Java dispatch semantics to take advantage of 'covariance'. The rest of this page will try to explain in more detail - and provide links into the code.
In short, this document provides an example of the Visitor Pattern implemented using the Java Reflection API so that the system can readily accommodate adding or removing new types of objects. We also use reflection as a means of doing covariant dispatch to the visit(...) methods.
Before you argue that reflective dispatch is too slow, it's important to realize that using reflection has many advantages over the most common techniques, especially reduced coupling and control over dispatch.
One advantage is a reduction in coupling - which makes developing code easier; especially if you have not solidified the type hierarchy. Normally, a Visitor interface is defined with a set of methods, one for each type of object appearing in the graph that will be visited. It is problematic to have to change this interface as that set of types changes. To reduce the coupling between the interface and the types, the ReflectiveVisititor can be used. If performance turns out to be an issue ReflectiveVisitor can be used while the set of types that will be visited is in flux and it can then be replaced with a more traditional Visitor interface after the hierarchy is 'fixed.'
Using reflection can give you more control over the system, while allowing you to open it more fully for extension by third-party developers. For example, Java's method dispatcher does not allow for covariant specialization. With covariant dispatch, visit methods in the visitor are called based on the types of arguments provided. Basically, the dispatcher will find and use the method that most closely matches the actual types of the arguments provided. This example implements a form of covariant specialization, meaning that it searches for a method that takes a set of parameters that most closely matches the real types of the arguments passed at runtime.
The following code demonstrates the algorithm I use for covariant dispatch. It searches the visitor class and then its superclasses and interfaces for methods that are named 'visit' and that take parameters that match the type of given arguments. If it does not find a method taking those exact types, it searches for methods that take a superclass or interface of the parameters given. This is a tad difficult to explain, so here's the code for the search method:
and the code for the utility method that is called:
This is not the only possible search and the code that I provide allows you to insert your own searches, should you want to.
My sample code shows two variants of the Visitor pattern, the first is as simple as possible, the other allows the programmer to use a private protocol between the visitor and the visitable objects via a passback object.
Again, this implementation has an advantage over a strongly-typed straight forward approach where the typical implementation defines Visitors that have a visit method for each type of object that is going to be visited. It uses covariant dispatching to find the best matched method in the Visitor, based on the type of the Visitable object. This makes it much easier to extend the Visitor in a real system as coupling is dramatically reduced. It also makes it possible to add visitable object types after the fact.
The two versions of the pattern are:
All of the code is here. If you choose to use it (or the jar), please send me email (kent -at- thisdomain). I'd like to hear any feedback people might have.