Skip to main content

If you don't have an IBM ID and password, register here.

By clicking Submit, you agree to the developerWorks terms of use.

The first time you sign into developerWorks, a profile is created for you. This profile includes the first name, last name, and display name you identified when you registered with developerWorks. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

All information submitted is secure.

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerworks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

By clicking Submit, you agree to the developerWorks terms of use.

All information submitted is secure.

Diagnosing Java Code: Depth-first visitors and broken dispatches

This Visitor-pattern variant can increase the terseness of your code

Eric Allen (eallen@cs.rice.edu), Ph.D. candidate, Java programming languages team, Rice University
Eric Allen has a bachelor's degree in computer science and mathematics from Cornell University and is a PhD candidate in the Java programming languages team at Rice University. Before returning to Rice to finish his degree, Eric was the lead Java software developer at Cycorp, Inc. He has also moderated the Java Beginner discussion forum at JavaWorld. His research concerns the development of semantic models and static analysis tools for the Java language, both at the source and bytecode levels. Eric has also helped in the development of Rice's compiler for the NextGen programming language, an extension of the Java language with generic run-time types. Contact Eric at eallen@cs.rice.edu.

Summary:  In this installment of Diagnosing Java Code, Eric Allen discusses how it's possible to increase the terseness of your code through the use of depth-first visitors, a variant on the Visitor pattern. He describes the technique, discusses the advantages and the "gotchas," cautions the reader about the bug patterns associated with its use, and illustrates depth-first visitors in the context of a specific example. After reading this article, you'll understand how to use depth-first visitors to your coding advantage, as well as be aware of the pitfalls of employing this technique.

View more content in this series

Date:  01 Jan 2002
Level:  Intermediate

Comments:  

Design patterns, at their best, can be a great help in quickly converging on a simple design for a project. But the simplest way to implement a design pattern in a particular context is not always obvious -- there's more than one way to roast a goose. This month, we'll discuss some ways to apply common design patterns to produce simple, short, and robust code.

First, let's look at two patterns with applicability in many different contexts. Of all the design patterns discussed in Design Patterns (Erich Gamma, et al., a.k.a. the "Gang of Four"), the seminal work on this topic (see Resources), I've found that the most widely applicable are the Composite and Visitor patterns.

Let's take a closer look at these two patterns.

Specifying a recursive datatype with Composite

It's easy to see why the Composite pattern is useful. The Composite pattern is simply the object-oriented way to specify a recursively defined datatype; these opportunities pop up all the time in software development.

The ability to work over recursively defined types of objects is one of the most salient characteristics of software engineering (as opposed to, say, digital design, where the systems are built from finite-state machines).


Extending class hierarchies with Visitor

As for the Visitor pattern, much of the reason it is so widely applicable is the fact that it complements the Composite pattern.

Visitors are often touted as a means of extending the functionality of an existing composite class hierarchy without actually modifying the classes in that hierarchy. But their abilities go much further than that.

Because visitors allow you to separate some of the functionality of a class hierarchy from the classes themselves, they can be used in all sorts of settings where it is conceptually awkward to consider the functionality to be an intrinsic part of those classes.

One common case in which this occurs frequently is in a recursive descent over a composite data structure. For example, consider a class hierarchy for binary trees and a visitor that determines if any node in a tree contains a zero:

abstract class Tree {
  public abstract Boolean accept(TreeVisitor that);
}

class Leaf extends Tree {
  public static final Leaf ONLY = new Leaf();

  public Boolean accept(TreeVisitor that) {
    return that.forLeaf(this);
  }
}

class Branch extends Tree {

  public int value;
  public Tree left;
  public Tree right;

  public Branch(int _value, Tree _left, Tree _right) {
    this.value = _value;
    this.left = _left;
    this.right = _right;
  }

  public Boolean accept(TreeVisitor that) {
    return that.forBranch(this);
  }
}

interface TreeVisitor {
  public Boolean forLeaf(Leaf that);
  public Boolean forBranch(Branch that);
}

class ZeroFinder implements TreeVisitor {
  public Boolean forLeaf(Leaf that) {
    return new Boolean(false);
  }

  public Boolean forBranch(Branch that) {
    if (that.value == 0) { return new Boolean(true); }

    boolean foundInLeft = that.left.accept(this).booleanValue();
    boolean foundInRight = that.right.accept(this).booleanValue();

    return new Boolean(foundInLeft || foundInRight);
  }
}

By keeping this zero-finding functionality in for*() methods ("*" representing the wildcard such as "Leaf" or "Branch"), separate from the Tree classes themselves, we gain these advantages:

  • We prevent code bloat of these classes. If we add in new methods to our classes every time there is a need for one more piece of functionality, they'll get big pretty quickly.

  • We keep all of the zero-finding functionality in one place, making it easier to maintain.

When visitors are bad

Now, as the Gang of Four mentioned, the main disadvantage of using visitors is that it becomes a little harder to add new classes into the composite hierarchy. This said, visitors are best used in cases where the composite hierarchy is not expected to change (although there are ways around this, such as the "Extensible Visitor pattern").

However, there is another big disadvantage. In cases where many of the classes in the composite hierarchy are handled in the same way, the code will be a lot larger in visitors than it would be if put in the classes themselves (since we could simply put a default implementation into the parent class).

Fortunately, there is a variant on ordinary visitors that not only solves this issue, but actually allows us, in many cases, to make the code even simpler than it would be if put into the individual classes. We call this variant the Depth-First Visitor pattern.


Depth-first visitor pattern

The idea behind the Depth-First Visitor pattern is as follows: most visitors that recursively descend down a data structure do so in a depth-first manner. That means they visit the children of a given (non-leaf) node before visiting the node itself.

Therefore, we can implement the depth-first traversal in the for* methods of an abstract visitor class and then let the concrete implementations simply describe, for each datatype, how to combine the results of visiting the subnodes. These descriptions are placed in special for*Only() methods that take as arguments the results of visiting their subnodes.

For example, Listing 2 shows how we could rewrite a depth-first visitor over our binary trees:

abstract class DepthFirstTreeVisitor implements TreeVisitor {

  public abstract Boolean forLeafOnly(Leaf that);
  public abstract Boolean forBranchOnly(Branch that,
                                        Boolean left_result,
                                        Boolean right_result);

  public Boolean forLeaf(Leaf that) {
    return forLeafOnly(that);
  }

  public Boolean forBranch(Branch that) {
    Boolean left_result = that.left.accept(this);
    Boolean right_result = that.right.accept(this);

    return forBranchOnly(that, left_result, right_result);
  }
}

Now we can rewrite our ZeroFinder visitor as shown in Listing 3.

abstract class DepthFirstTreeVisitor implements TreeVisitor {

class DepthFirstZeroFinder extends DepthFirstTreeVisitor {
  public Boolean forLeafOnly(Leaf that) {
    return new Boolean(false);
  }

  public Boolean forBranchOnly(Branch that, 
		               Boolean left_result, 
                               Boolean right_result) 
  {
    if (that.value == 0) { return new Boolean(true); }
    return new Boolean(left_result.booleanValue() || 
                       right_result.booleanValue());
  }
}

With this, we've managed to pull much of the complexity of the depth-first traversal out of the concrete visitors and into the (shared) abstract parent class. Also, the implementor of a depth-first visitor would have handy (in the signatures of the for*Only() methods) a list of all contained components for each type.


What about complex hierarchies?

This is about as good as we can do for this simple composite hierarchy. For more complex class hierarchies, however, we can do more.

Often, for composite hierarchies of many classes, a given visitor will handle many of the cases in a common (or trivial) way. This is especially true when the visitor substitutes for a couple instanceof checks. For these cases, we can provide default implementations of the for*Only() methods that simply call an abstract defaultCase() method:

abstract class DepthFirstTreeVisitor implements TreeVisitor {

  public abstract Boolean defaultCase(Tree that);

  public Boolean forLeafOnly(Leaf that) {
    return defaultCase(that);
  }

  public Boolean forBranchOnly(Branch that,
                               Boolean left_result,
                               Boolean right_result)
  {
    return defaultCase(that);
  }
  ...
}

Now, when implementing a depth-first visitor over such a composite, we need only specify code for the cases that aren't taken care of by the default.

When the composite hierarchy is deeper than one layer, we may also want to provide separate default methods for each subtree of the hierarchy. These default methods can be defined to simply call the default method of the parent class. But they can be overridden as necessary.

Once we've done that, we've gained the terseness of including functionality in the classes of the composite while keeping all the advantages of ordinary visitors.


Of course, the warning

For visitors that descend depth-first in all but a few cases, we can still use a depth-first visitor. We just have to override those for*() methods (as opposed to the for*Only() methods) that we don't want to traverse depth-first.

There is one caveat to this approach, however: it's really easy to encounter broken dispatches this way. Remember them? The Broken Dispatch bug pattern occurs when we accidentally overload a method rather than overriding its implementation in the parent class. With depth-first visitors, this is especially easy to do.

Suppose that we intend to override one of the for*() (but not the for*Only()) methods of the parent class. The signature of this for*() method will take the node it operates on, but, unlike a for*Only() method, it won't take the results of visiting the subnodes. Now, because we're writing both for*() and for*Only() methods, it's not hard to imagine that we'll slip and accidentally write for*Only() as the name of one of our for*() methods.

If we do so, the code will compile just fine, but we'll have failed to override the for*() method. Instead, we'll have overloaded the for*Only() method with a new method of a different signature.

Additionally, this new for*Only() method will never be called. Broken dispatches like these can be particularly insidious bugs to track down -- the type checker won't catch them and you never know when, or if, they'll ever actually signal an error. In the worst-case scenario, broken dispatches will simply modify the behavior of your program in such a way that the results appear to be perfectly reasonable, but are, in fact, dead wrong.

All I can say about preventing them is that they're the zillionth example of why it saves time to write unit tests before (and while) writing code, and why you should run those tests often!

So there you have it: depth-first visitors, why they're cool, and how they can bite you. I'd like to thank graduate student Brian Stoler of our research lab for first introducing me to this pattern. Depth-first visitors are constructed by the "Java Tree Builder," a free tool available in Resources for automatically generating a composite hierarchy (plus visitors) from a JavaCC grammar.


Resources

  • If you're new to design patterns, you might want to start with "Java design patterns 101" (developerWorks, January 2002), which provides an introduction to an important and useful object-oriented design and development concept.

  • Start generating depth-first visitors automatically with the Java Tree Builder, a syntax tree builder used with the Java Compiler Compiler (JavaCC) parser generator. It takes a plain JavaCC grammar file as input and automatically generates the following: a set of syntax tree classes based on the productions in the grammar (using the Visitor design pattern); two interfaces (Visitor and ObjectVisitor); two depth-first visitors (DepthFirstVisitor and ObjectDepthFirst); and a JavaCC grammar with the proper annotations to build the syntax tree during parsing.

  • The JUnit Web site provides links to many interesting articles from a multitude of sources that discuss program testing methods.

  • Read all of Eric Allen's Diagnosing Java Code columns.

  • Find more Java resources on the developerWorks Java technology zone.

About the author

Eric Allen has a bachelor's degree in computer science and mathematics from Cornell University and is a PhD candidate in the Java programming languages team at Rice University. Before returning to Rice to finish his degree, Eric was the lead Java software developer at Cycorp, Inc. He has also moderated the Java Beginner discussion forum at JavaWorld. His research concerns the development of semantic models and static analysis tools for the Java language, both at the source and bytecode levels. Eric has also helped in the development of Rice's compiler for the NextGen programming language, an extension of the Java language with generic run-time types. Contact Eric at eallen@cs.rice.edu.

Report abuse help

Report abuse

Thank you. This entry has been flagged for moderator attention.


Report abuse help

Report abuse

Report abuse submission failed. Please try again later.


developerWorks: Sign in

If you don't have an IBM ID and password, register here.


Forgot your IBM ID?


Forgot your password?
Change your password


By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. This profile includes the first name, last name, and display name you identified when you registered with developerWorks. Select information in your developerWorks profile is displayed to the public, but you may edit the information at any time. Your first name, last name (unless you choose to hide them), and display name will accompany the content that you post.

Choose your display name

The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

(Must be between 3 – 31 characters.)


By clicking Submit, you agree to the developerWorks terms of use.

 


Rate this article

Comments

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology
ArticleID=10622
ArticleTitle=Diagnosing Java Code: Depth-first visitors and broken dispatches
publish-date=01012002
author1-email=eallen@cs.rice.edu
author1-email-cc=

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

For articles in technology zones (such as Java technology, Linux, Open source, XML), Popular tags shows the top tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), Popular tags shows the top tags for just that product zone.

For articles in technology zones (such as Java technology, Linux, Open source, XML), My tags shows your tags for all technology zones. For articles in product zones (such as Info Mgmt, Rational, WebSphere), My tags shows your tags for just that product zone.

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).