Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Design patterns, the big picture, Part 2: Gang-of-four classics revisited

Learn the GoF design patterns in Java code, starting with Strategy and Visitor

  • Print
  • Feedback

Page 3 of 4

Do we over-rely on double dispatch?

Blogger Derek Greer believes that using double dispatch may indicate a design issue, which could impact an application's maintainability. Read Greer's "Double dispatch is a code smell" blog post and associated comments for details.

Simulating double dispatch in Java code

Wikipedia's entry on double dispatch provides a C++-based example that shows it to be more than function overloading. In Listing 6, I present the Java equivalent.

Listing 6. Double dispatch in Java code

public class DDDemo
{
   public static void main(String[] args)
   {
      Asteroid theAsteroid = new Asteroid();
      SpaceShip theSpaceShip = new SpaceShip();
      ApolloSpacecraft theApolloSpacecraft = new ApolloSpacecraft();
      theAsteroid.collideWith(theSpaceShip);
      theAsteroid.collideWith(theApolloSpacecraft);
      System.out.println();

      ExplodingAsteroid theExplodingAsteroid = new ExplodingAsteroid();
      theExplodingAsteroid.collideWith(theSpaceShip);
      theExplodingAsteroid.collideWith(theApolloSpacecraft);
      System.out.println();

      Asteroid theAsteroidReference = theExplodingAsteroid;
      theAsteroidReference.collideWith(theSpaceShip);
      theAsteroidReference.collideWith(theApolloSpacecraft);
      System.out.println();

      SpaceShip theSpaceShipReference = theApolloSpacecraft;
      theAsteroid.collideWith(theSpaceShipReference);
      theAsteroidReference.collideWith(theSpaceShipReference);
      System.out.println();

      theSpaceShipReference = theApolloSpacecraft;
      theAsteroidReference = theExplodingAsteroid;
      theSpaceShipReference.collideWith(theAsteroid);
      theSpaceShipReference.collideWith(theAsteroidReference);
   }
}

class SpaceShip
{
   void collideWith(Asteroid inAsteroid)
   {
      inAsteroid.collideWith(this);
   }
}

class ApolloSpacecraft extends SpaceShip
{
   void collideWith(Asteroid inAsteroid)
   {
      inAsteroid.collideWith(this);
   }
}

class Asteroid
{
   void collideWith(SpaceShip s)
   {
      System.out.println("Asteroid hit a SpaceShip");
   }

   void collideWith(ApolloSpacecraft as)
   {
      System.out.println("Asteroid hit an ApolloSpacecraft");
   }
}

class ExplodingAsteroid extends Asteroid
{
   void collideWith(SpaceShip s)
   {
      System.out.println("ExplodingAsteroid hit a SpaceShip");
   }

   void collideWith(ApolloSpacecraft as)
   {
      System.out.println("ExplodingAsteroid hit an ApolloSpacecraft");
   }
}

Listing 6 follows its C++ counterpart as closely as possible. The final four lines in the main() method along with the void collideWith(Asteroid inAsteroid) methods in SpaceShip and ApolloSpacecraft demonstrate and simulate double dispatch.

Consider the following excerpt from the end of main():

theSpaceShipReference = theApolloSpacecraft;
theAsteroidReference = theExplodingAsteroid;
theSpaceShipReference.collideWith(theAsteroid);
theSpaceShipReference.collideWith(theAsteroidReference);

The third and fourth lines use single dispatch to figure out the correct collideWith() method (in SpaceShip or ApolloSpacecraft) to invoke. This decision is made by the virtual machine based on the type of the reference stored in theSpaceShipReference.

From within collideWith(), inAsteroid.collideWith(this); uses single dispatch to figure out the correct class (Asteroid or ExplodingAsteroid) containing the desired collideWith() method. Because Asteroid and ExplodingAsteroid overload collideWith(), the type of argument this (SpaceShip or ApolloSpacecraft) is used to distinguish the correct collideWith() method to call.

And with that, we have accomplished double dispatch. To recap, we first called collideWith() in SpaceShip or ApolloSpacecraft, and then used its argument and this to call one of the collideWith() methods in Asteroid or ExplodingAsteroid.

When you run DDDemo, you should observe the following output:

Asteroid hit a SpaceShip
Asteroid hit an ApolloSpacecraft

ExplodingAsteroid hit a SpaceShip
ExplodingAsteroid hit an ApolloSpacecraft

ExplodingAsteroid hit a SpaceShip
ExplodingAsteroid hit an ApolloSpacecraft

Asteroid hit a SpaceShip
ExplodingAsteroid hit a SpaceShip

Asteroid hit an ApolloSpacecraft
ExplodingAsteroid hit an ApolloSpacecraft

Now that we've fully unpacked the double dispatch technique, let's return to the Visitor pattern.

Using Visitor to print and evaluate abstract syntax trees

Design Patterns uses a compiler context as the Visitor pattern's motivation. Although Visitor can be used in any hierarchical context where you don't want to mix operations on a given structure with the structure's elements, we'll follow the GoF example to study this pattern.

A compiler converts source code into executable code. The analysis phase scans characters into tokens, parses the tokens to verify syntactic correctness, and creates an abstract syntax tree (AST) that represents the program as a hierarchy of nodes.

The AST is then type-checked to verify semantic correctness (variables must be declared before they are used, for example). This data structure is also commonly used to drive code generation. These operations are often performed by visitors.

Consider an expression language involving addition, subtraction, multiplication, division, negation, floating-point numbers, and parentheses. An expression AST features nodes representing all of these items except for parentheses, which determine the tree structure.

AST visitors visit these nodes and are based on a class hierarchy that's rooted in an abstract class or interface. The root type abstracts methods for visiting the different kinds of nodes, and is extended by concrete subclasses that implement specific kinds of visitors, as shown in Figure 1.

Figure 1. The Visitor pattern is partly based on a hierarchy of visitor classes (click to enlarge)

Figure 1 reveals a convention in which each visitor method begins with the word visit and ends with the name of the node class. Furthermore, the argument passed to this method has the node class as its type.

Each concrete AST class declares an accept(Visitor) method that receives a visitor class instance as its argument. This method invokes the appropriate visitor method on this argument, and passes a reference to itself as the invoked method's argument, as illustrated by Figure 2.

Figure 2. The Visitor pattern is partly based on a client-specific hierarchy of node classes

Figure 2 describes an AST for the expression: 4.0+2.0*3.0. For brevity, only the code executed by NumNode's accept(Visitor) method is shown. The getName() method is used to return the name of the node, and is only invoked by the print visitor.

  • Print
  • Feedback

Resources

David Geary's Java design patterns series on JavaWorld: Learn more of the Gang of Four patterns in Java code: