Click Here!
Search:
Locator+ Code:
FTP Home   Javapro Home   Archives   Customer Service   Site Map
 
A Time for Reflection

Java 1.2's reflection capabilities eliminate burdensome accept() methods from your Visitor pattern

by Bruce Wallace
  In the November 2000 issue of Java Pro, James Cooper surveyed the well-known Visitor pattern. He explained that every element to be visited needs to cooperate by providing an accept() method. This constraint can sometimes be very burdensome. However, a technique is presented in Java 2 that eliminates accept() methods altogether, with only two lines of code, using new reflection capabilities.

The Problem with Acceptance
The Visitor pattern seeks to add functionality to some arbitrary set of classes by factoring out the new functionality into a visitor class. The classes being enhanced by the visitor are known as elements.

In all of the variations of the Visitor pattern, one mechanism or another is used to traverse some set of element objects, visiting each one. However, to do this in the past, each element had to define an accept() method. This method would take a Visitor object as a parameter and call back that Visitor's visit() method, passing itself as a parameter. A typical accept() method would look like this:


public void accept(Visitor v)
{
v.visit(this);
}

and simple traversal code might look like:


Visitor v = new Visitor();
for (int i=0; i<elementList.length; ++i) elementList[i].accept(v);


It would be simpler and cleaner if the element classes could remain unaware of the visitations. This is often not just a matter of elegance. When working with external libraries or frameworks, where you don't have control over the source code, accept() methods can't just be added to the classes you would like to visit. Usually this problem is addressed by counseling the developer to create subclasses of these unchangeable element classes. The subclasses can add the needed accept() method. However, this approach still has problems; you usually can't change those same libraries and frameworks to use your new subclass constructors when they instantiate the elements you want to visit.

So, why is accept() used at all? Since most OO languages support what Design Patterns calls single-dispatch method calls, and the Visitor pattern needs double-dispatch method calls, the accept() method is the traditional way to simulate a double-dispatch capability. In other words, the version of the visit() method invoked should depend on both the class of the visitor and the class of each element being visited.

However, since the Visitor class must add a version of visit() for each element class that it wishes to visit anyway, why not just call visit() directly? Why not pass visit() the element as follows?

for (int i=0; i<elementList.length; ++i) v.visit(elementList[i]);

While this would work when all the elements are the same class, it won't handle the common situation where elementList holds not simply Element objects, but a mix of objects that are subclasses of Element. Or with the most general case in Java, imagine elementList is an array of Object. The actual class of each element could be anything. In that case, no matter what the real class of each element object was, the method with the visit(Object) signature would be invoked.

The Magic of Reflection
In Java 1.2, some new reflection capabilities were added. The new getMethod() call allows you to determine, at runtime, the method of a class that has parameters with specified types. Another new method, invoke(), lets you execute the methods found with getMethod().

A program that uses these new capabilities is shown in Listing 1, which demonstrates a Visitor that doesn't need accept() methods. This program defines three arbitrary element classes (Circle, Square, and Beagle) that we wish to enhance with a Visitor. To keep things brief, MyVisitor doesn't enhance these classes much, but it does follow the Visitor pattern by defining a visit() method for each one. It also adds a generic visit() method that has Object as its argument type. The magic two lines of code in this method use the reflection API to determine the real class of the element parameter. The appropriate visit() method for the element's actual class is found and invoked. Finally, a TestBed main program is defined. It instantiates elements of various classes and collects them into an Object array. It then creates a visitor object and invokes it while traversing the element collection.

This technique can be used with any collection of element classes, and with any type of collection traversal or iteration technique. Also, the traversal can be managed by the element collection, the visitor, or a third party, without restriction. The only condition for using this technique is that you must use Java 2 for your Visitor objects. But since the elements themselves don't need to be modified, they don't have to be Java 2 objects.


Bruce Wallace is president of PolyGlot, Inc., a software development consultancy. He can be reached at bruce.wallace@acm.org.

 







What did you think of this article? Send your e-mail to the editors at java-pro@fawcette.com.

© 2000 Fawcette Technical Publications.