XNSIO
  About   Slides   Home  

 
Managed Chaos
Naresh Jain's Random Thoughts on Software Development and Adventure Sports
     
`
 
RSS Feed
Recent Thoughts
Tags
Recent Comments

Double Dispatch Demystified

Single Dispatch: In most object-oriented programming languages, the concrete method that is invoked at runtime depends on the dynamic (runtime) type of the object on which the method is invoked.

For Example: If we have 2 cars; Car and derived class RubberCar.

public class Car
{
    public virtual string CrashInto(Wall wall)
    { 
        return "Car crashed into the Wall";
    }
}
 
public class RubberCar: Car
{
    public override string CrashInto(Wall wall)
    {
        return "Rubber Car crashed into the Wall";
    }
}

and its test code

[Test]
public void SingleDispathWorksCorrectly()
{
    Wall wall = new Wall();
    Car currentCar = new Car();
    Assert.AreEqual("Car crashed into the Wall", currentCar.CrashInto(wall));
    currentCar = new RubberCar();
    Assert.AreEqual("Rubber Car crashed into the Wall", currentCar.CrashInto(wall));
}

First time we call the method CrashInto() on currentCar, we are holding the reference of Car and hence Car’s CrashInto() method is invoked. However the second time, we’re holding the reference of RubberCar in currentCar and CrashInto() method from RubberCar is invoked correctly. In other words this is referred to as Polymorphism.

Its called Single Dispatch because one object’s runtime type (object on which the method is invoked) is used to decide which concrete method to invoke.

Double Dispatch: A mechanism that dispatches a method call to different concrete methods depending on the runtime types of two objects involved in the call (object and its parameter)

Let’s say, we had 2 types of wall, Wall and the derived class MagicWall.

Now when we called currentCar.CrashInto(new MagicWall()) we want to execute different behavior compared to currentCar.CrashInto(new Wall()). One classic way to implement this is:

// Car class
public virtual string CrashInto(Wall wall)
{
    if (wall is MagicWall)
        return "Car crashed into the Magic Wall";
    return "Car crashed into the Wall";
}
// RubberCar class
public override string CrashInto(Wall wall)
{
    if (wall is MagicWall)
        return "Rubber Car crashed into the Magic Wall";
    return "Rubber Car crashed into the Wall";
}

This of-course works. However an alternative way to avoid the Switch Statement Smell and to truly use Double Dispatch is:

In Car:

public virtual string CrashInto(Wall wall)
{
    return "Car crashed into the Wall";
}
 
public virtual string CrashInto(MagicWall wall)
{
    return "Car crashed into the Magic Wall";
}

and in RubberCar

public override string CrashInto(Wall wall)
{
    return "Rubber Car crashed into the Wall";
}
 
public override string CrashInto(MagicWall wall)
{
    return "Rubber Car crashed into the Magic Wall";
}

and its respective test:

[Test]
public void CarCanCrashIntoTheWall()
{
    Wall wall = new Wall();
    Assert.AreEqual("Car crashed into the Wall", new Car().CrashInto(wall)); 
}
 
[Test]
public void CarCanCrashIntoTheMagicWall()
{
    MagicWall wall = new MagicWall();
    Assert.AreEqual("Car crashed into the Magic Wall", new Car().CrashInto(wall));
}

and

[Test]
public void RubberCarCanCrashIntoTheWall()
{
    Wall wall = new Wall();
    Assert.AreEqual("Rubber Car crashed into the Wall", new RubberCar().CrashInto(wall)); 
}
 
[Test]
public void RubberCarCanCrashIntoTheMagicWall()
{
    MagicWall wall = new MagicWall();
    Assert.AreEqual("RubberCar crashed into the Magic Wall", new RubberCar().CrashInto(wall));
}

Believe it or not, this is Double Dispatch in action. Which concrete method to invoke, depends on runtime type of 2 objects (car and wall.)

However there is a catch with this method. If you did the following:

[Test]
public void MethodOverloadingTakesPlaceAtCompileTime()
{
    Wall wall = new MagicWall();
    Assert.AreEqual("Car crashed into the Wall", new Car().CrashInto(wall));
    //   Instead of "Car Crashed into the Magic Wall"
}

Since MagicWall’s reference is held in wall, which is of type Wall at compile type and overloaded method are bound at compile time, this method behaves unexpectedly.

One way to fix this issue is to use vars in .Net.

[Test]
public void UseOfVarForcesMethodOverloadingToTakesPlaceAtRunTime()
{
    var wall = new MagicWall();
    Assert.AreEqual("Car crashed into the Magic Wall", new Car().CrashInto(wall));
}

In Languages that don’t support Double Dispatch, one needs to do the following:

[Test]
public void ToAvoidTheConfusion()
{
    Wall wall = new MagicWall();
    Assert.AreEqual("Car crashed into the Magic Wall", wall.CollidesWith(car));
    Assert.AreEqual(0, car.DentCount);
}
 
[Test]
public void DoubleDispatch_OnWall()
{
    Wall wall = new Wall();
    Assert.AreEqual("Car crashed into the Wall", wall.CollidesWith(car));
    Assert.AreEqual(1, car.DentCount);
}

Production Code:

public class Wall
{
    public virtual string CollidesWithCar(Car car)
    {
        return car.CrashIntoWall(this);
    }   
 
    public virtual string CollidesWithRubberCar(RubberCar car)
    {
        return car.CrashIntoWall(this);
    }
}
 
public class MagicWall : Wall
{ 
    public override string CollidesWithCar(Car car)
    {
        return car.CrashIntoMagicWall(this);
    } 
 
    public override string CollidesWithRubberCar(RubberCar car)
    {
        return car.CrashIntoMagicWall(this);
    }
}
public class Car
{ 
    public virtual string CrashIntoWall(Wall wall)
    { 
        return "Car crashed into the Wall";
    }
 
    public virtual string CrashIntoMagicWall(Wall magicWall)
    {
        return "Car crashed into the Magic Wall";
    }
}
 
public class RubberCar:Car
{
    public override string CrashIntoWall(Wall wall)
    {
        return "Rubber Car crashed into the Wall";
    }
 
    public override string CrashIntoMagicWall(Wall magicWall)
    {
        return "Rubber Car crashed into the Magic Wall";
    }      
}

We can expand the same technique to use more than 2 objects to decide which concrete method to invoke at run time. This mechanism is called Multiple Dispatch.


    Licensed under
Creative Commons License