SDSU CS 535 Object-Oriented Programming & Design
Spring Semester, 1999
Double Dispatching
Previous    Lecture Notes Index    Next    
© 1999, All Rights Reserved, SDSU & Roger Whitney
San Diego State University -- This page last updated 06-May-99

Contents of Doc 19, Double Dispatching


Reference

Design Patterns: Elements of Reusable Object-Oriented Software, Gamma, Helm, Johnson, Vlissides, pp. 338-339

Doc 19, Double Dispatching Slide # 2

Double-Dispatching


We have the following class structure


Different types of potions will have different effect on different creatures. For example a TrollPotion may kill a Troll, but have no effect on an Ant. An AntPotion may kill an ant, make a troll very upset and have no effect on other creatures.

We have a spray method in the Potions classes

class Potion {
   public void spray( Creature aCreature ) {}
}
We would like the spray method to perform differently depending on the type of potion and the type of creature without using instanceof and cases statements on types.

In particular we would like the following code to perform properly

Potion container = either Potion, TrollPotion or AntPotion;
Creature aCreature = either Creature, Troll or Ant;
container.spray( aCreature );
What we want is double-dispatching. Java and C++ directly support single-dispatch. That is when a method is called the type of the receiver and the method name (signature) determines what method will actually be called. In double-dispatching the method called depends on the method name (signature), and the actual types of two receivers. CLOS supports this directly. In Java and C++ we need to do some work to achieve that. Solution 1 and 2 do this.

Doc 19, Double Dispatching Slide # 3

First Attempt

public class Potion {
   public void spray( Creature aType ) {
      System.out.println( "Generic spray" );   
   }
}
public class TrollPotion extends Potion {
   public void spray( Troll aTroll ) {
      System.out.println( "TrollPotion's spray with Troll" );
   }
}
public class Creature { }
public class Troll extends Creature { }
Test 1
Potion container = new TrollPotion();
container.spray( new Troll() );
container.spray( new Creature () );

Output
Generic spray
Generic spray
Test 2
TrollPotion container = new TrollPotion();
container.spray( new Troll() );
container.spray( new Creature() );
Output
TrollPotion's spray with Troll
Generic spray

Note that this only works when we have explicit reference to a TrollPotion.

Doc 19, Double Dispatching Slide # 4

Second Attempt

The Potion needs to have a spray method for each creature type. This will allow the code to work properly when we have a variable of type Potion contain a TrollPotion.

public class Potion
   {
   public void spray( Creature aType )
      {
      System.out.println( "Generic spray" );   
      }
   public void spray( Troll aType )
      {
      System.out.println( "Generic troll spray" );   
      }
   public void performSpray( Creature aType )
      {
      System.out.println( "Potion's performSpray with Creature" );   
      }
   }
public class TrollPotion extends Potion
   {
   public void spray( Troll aTroll )
      {
      System.out.println( "TrollPotion's spray with Troll" );
      }
   }

Doc 19, Double Dispatching Slide # 5
Test 1
Potion container = new TrollPotion();
container.spray( new Troll() );
container.spray( new Creature() );
Output
TrollPotion's spray with Troll
Generic spray

Test 2
Potion container = new TrollPotion();
Creature aCreature = new Troll();
container.spray( aCreature );
aCreature = new Creature();
container.spray( aCreature );

Output
Generic spray
Generic spray
Now the problem is that if we have a creature varible with a Troll object our code does not work properly.

Doc 19, Double Dispatching Slide # 6
A Bounce
Use polymorphism on the creature type solves that problem. Here is a simple solution.

public class Potion
   {
   public void spray( Creature aType )
      {
      aType.beingSprayed( this );
      }
   }
public class TrollPotion extends Potion {   }
public class Creature
   {
   public void beingSprayed( Potion aPotion )
      {
      System.out.println( "Creature's beingSprayed with Potion" );
      }
   }
public class Troll extends Creature
   {
   public void beingSprayed( Potion aPotion )
      {
      System.out.println( "Troll's beingSprayed with Potion" );
      }
   }
Test
Potion container = new TrollPotion();
Creature aCreature = new Troll();
container.spray( aCreature );
aCreature = new Creature();
container.spray( aCreature );
Output
Generic spray
Troll's beingSprayed with Potion
Generic spray
Creature's beingSprayed with Potion
Now we have the Creature type take care of. Since types are always determined statically in java in Potion method the "this" in "aType.beingSprayed( this )" is always treated as type Potion.

Doc 19, Double Dispatching Slide # 7

Solution 1

Adding the spray method in TrollPotion will solve this latest problem. This is a bit strange looking. All subclasses of Potion must implement the same method and it can be identical to the parent’s method. If the Potion class is abstract (or an interface) or the subclasses perform additional operations it will not look so bad. However, this requirement needs to be documented in the code. It if is not, it will cause problems later on - someone will remove the identical code or not add it to new subclasses.
public class Potion
   {
   public void spray( Creature aType )
      {
      aType.beingSprayed( this );
      }
   }
public class TrollPotion extends Potion
   {
   public void spray( Creature aType)
      {
      aType.beingSprayed( this );
      }
   }
public class Creature
   {
   public void beingSprayed( Potion aPotion )
      {
      System.out.println( "Creature's beingSprayed with Potion" );
      }
   public void beingSprayed( TrollPotion aPotion )
      {
      System.out.println( "Creature's beingSprayed with TrollPotion" );
      }
   }
public class Troll extends Creature
   {
   public void beingSprayed( Potion aPotion )
      {
      System.out.println( "Troll's beingSprayed with Potion" );
      }
   public void beingSprayed( TrollPotion aPotion )
      {
      System.out.println( "Troll's beingSprayed with TrollPotion" );
      }
   }

Doc 19, Double Dispatching Slide # 8
Note that these tests work properly.

Test
Potion container = new TrollPotion();
Creature aCreature = new Troll();
container.spray( aCreature );
aCreature = new Creature();
container.spray( aCreature );
Output
Troll's beingSprayed with TrollPotion
Creature's beingSprayed with TrollPotion

Test
Potion container = new Potion();
Creature aCreature = new Troll();
container.spray( aCreature );
aCreature = new Creature();
container.spray( aCreature );

Output
Troll's beingSprayed with Potion
Creature's beingSprayed with Potion

Doc 19, Double Dispatching Slide # 9

Solution 2 – Double Bounce

In the first solution the Creature classes knew about the Potion classes. While the Potion classes do not have any methods that directly know about the Creature classes, the Potion class structure already mirrors the Creature classes. In this solution, the Creature classes have no direct knowledge of any Potion classes. The Potion classes have direct knowledge of the Creature classes.
public class Potion
   {
   public void spray( Creature aType )
      {
      aType.beingSprayed( this );
      }
   public void performSpray( Creature aType )
      {
      System.out.println( "Potion's performSpray with Creature" );
      } 
   public void performSpray( Troll aType )
      {
      System.out.println( "Potion's performSpray with Troll" );
      } 
   }
public class TrollPotion extends Potion
   {
   public void performSpray( Creature aType )
      {
      System.out.println( "TrollPotion's performSpray with Creature" );   
      }
   public void performSpray( Troll aType )
      {
      System.out.println( "TrollPotion's performSpray with Troll" );
      }
   }
public class Creature
   {
   public void beingSprayed( Potion aPotion )
      {
      aPotion.performSpray( this );
      }
   }
public class Troll extends Creature
   {
   public void beingSprayed( Potion aPotion )
      {
      aPotion.performSpray( this );
      }
   }

Doc 19, Double Dispatching Slide # 10
Test
Potion container = new TrollPotion();
Creature aCreature = new Troll();
container.spray( aCreature );
aCreature = new Creature();
container.spray( aCreature );
Output
TrollPotion's performSpray with Troll
TrollPotion's performSpray with Creature


Copyright ©, All rights reserved.
1999 SDSU & Roger Whitney, 5500 Campanile Drive, San Diego, CA 92182-7700 USA.
OpenContent license defines the copyright on this document.

Previous    visitors since 06-May-99    Next