Multiple Dispatching

Introducción

Multiple dispatch or multimethods is the feature of some object-oriented programming languages in which a function or method can be dynamically dispatched based on the run time (dynamic) type of more than one of its arguments. This is an extension of single dispatch polymorphism where a method call is dynamically dispatched based on the actual derived type of the object. Multiple dispatch generalizes the dynamic dispatching to work with a combination of two or more objects.

Java

Java-style method overloading isn't multiple dispatch. In multiple dispatch, the 'switch' is done on the dynamic (run-time) type tag of the object - but Java's (and C++'s) overloading only considers the static (compile-time) type of the object reference.

Distinguishing multiple and single dispatch may be made clearer by an example. Imagine a game which has, among its (user-visible) objects, spaceships and asteroids. When two objects collide, the program may need to do different things according to what has just hit what.

generaciondecodigos@nereida:~/Lgroovy/objects/dispatch$ cat -n DoubleDispatchTest.java
 1  class SpaceShip {}
 2  class GiantSpaceShip extends SpaceShip {}
 3
 4  class Asteroid {
 5
 6    public void collideWith(SpaceShip sp) {
 7      System.out.println("Asteroid hit a SpaceShip");
 8    }
 9    public void collideWith(GiantSpaceShip gsp) {
10      System.out.println("Asteroid hit a GiantSpaceShip");
11    }
12  }
13
14  class ExplodingAsteroid extends Asteroid {
15
16    public void collideWith(SpaceShip sp) {
17      System.out.println("ExplodingAsteroid hit a SpaceShip");
18    }
19    public void collideWith(GiantSpaceShip gsp) {
20      System.out.println("ExplodingAsteroid hit a GiantSpaceShip");
21    }
22  }
23
24  public class DoubleDispatchTest {
25    public static void main(String args[]) {
26      Asteroid ast = new Asteroid();
27      Asteroid ast1 = new ExplodingAsteroid();
28      SpaceShip sp = new SpaceShip();
29      SpaceShip sp1  = new GiantSpaceShip();
30      ast.collideWith(sp);
31      ast.collideWith(sp1);
32      ast1.collideWith(sp);
33      ast1.collideWith(sp1);
34    }
35  }

Cuando se ejecuta se produce la siguiente salida:

generaciondecodigos@nereida:~/Lgroovy/objects/dispatch$ javac DoubleDispatchTest.java
generaciondecodigos@nereida:~/Lgroovy/objects/dispatch$ time java DoubleDispatchTest
Asteroid hit a SpaceShip
Asteroid hit a SpaceShip
ExplodingAsteroid hit a SpaceShip
ExplodingAsteroid hit a SpaceShip

real    0m0.106s
user    0m0.052s
sys     0m0.012s

This is due to the fact that, though Java can recognize the runtime type of the Asteroid, it ignores the runtime type of the SpaceShip which is sent as an argument.

El problema no aparece si declaramos los objetos sp1 y ast1 como GiantSpaceShip y ExplodingAsteroid respectivamente:

~/Lgroovy/objects/dispatch$ cat -n DoubleDispatchTest1.java 
 1  class SpaceShip {}
 2  class GiantSpaceShip extends SpaceShip {}
 3  
 4  class Asteroid {
 5  
 6    public void collideWith(SpaceShip sp) {
 7      System.out.println("Asteroid hit a SpaceShip");
 8    }
 9    public void collideWith(GiantSpaceShip gsp) {
10      System.out.println("Asteroid hit a GiantSpaceShip");
11    }
12  }
13  
14  class ExplodingAsteroid extends Asteroid {
15  
16    public void collideWith(SpaceShip sp) {
17      System.out.println("ExplodingAsteroid hit a SpaceShip");
18    }
19    public void collideWith(GiantSpaceShip gsp) {
20      System.out.println("ExplodingAsteroid hit a GiantSpaceShip");
21    }
22  }
23  
24  public class DoubleDispatchTest1 { 
25    public static void main(String args[]) { 
26      Asteroid ast = new Asteroid(); 
27      ExplodingAsteroid ast1 = new ExplodingAsteroid(); 
28      SpaceShip sp = new SpaceShip(); 
29      GiantSpaceShip sp1  = new GiantSpaceShip();
30      ast.collideWith(sp);
31      ast.collideWith(sp1);
32      ast1.collideWith(sp);
33      ast1.collideWith(sp1);
34    }
35  }
Ahora la salida es la esperada:
~/Lgroovy/objects/dispatch$ javac DoubleDispatchTest1.java 
~/Lgroovy/objects/dispatch$ java DoubleDispatchTest1
Asteroid hit a SpaceShip
Asteroid hit a GiantSpaceShip
ExplodingAsteroid hit a SpaceShip
ExplodingAsteroid hit a GiantSpaceShip

Groovy

En Groovy se consulta el tipo del objeto en tiempo de ejecución. La consecuencia es que aún cuando los objetos hayan sido declarados de la clase mas general, el método invocado es elegido en términos de los tipos actuales de los parámetros.

generaciondecodigos@nereida:~/Lgroovy/objects/dispatch$ cat -n DoubleDispatchTest.groovy
 1  class SpaceShip {}
 2  class GiantSpaceShip extends SpaceShip {}
 3
 4  class Asteroid {
 5
 6    public void collideWith(SpaceShip sp) {
 7      System.out.println("Asteroid hit a SpaceShip");
 8    }
 9    public void collideWith(GiantSpaceShip gsp) {
10      System.out.println("Asteroid hit a GiantSpaceShip");
11    }
12  }
13
14  class ExplodingAsteroid extends Asteroid {
15
16    public void collideWith(SpaceShip sp) {
17      System.out.println("ExplodingAsteroid hit a SpaceShip");
18    }
19    public void collideWith(GiantSpaceShip gsp) {
20      System.out.println("ExplodingAsteroid hit a GiantSpaceShip");
21    }
22  }
23
24  Asteroid ast = new Asteroid();
25  Asteroid ast1 = new ExplodingAsteroid();
26  SpaceShip sp = new SpaceShip();
27  SpaceShip sp1  = new GiantSpaceShip();
28  ast.collideWith(sp);
29  ast.collideWith(sp1);
30  ast1.collideWith(sp);
31  ast1.collideWith(sp1);
Cuando se ejecuta se produce la siguiente salida:
$ groovyc DoubleDispatchTest.groovy
$ time java -classpath \
    /usr/local/src/groovy/groovy-1.6.5/target/install/embeddable/groovy-all-1.6.5.jar:. \
    DoubleDispatchTest
Asteroid hit a SpaceShip
Asteroid hit a GiantSpaceShip
ExplodingAsteroid hit a SpaceShip
ExplodingAsteroid hit a GiantSpaceShip

real    0m0.737s
user    0m1.028s
sys     0m0.088s

Cuando es Útil Tener Multiple Dispatching

Damian Conway dice al respecto:

Typical situations where multiple dispatch is needed include:

Generally speaking, multiple dispatch is needed whenever two or more objects belonging to different class hierarchies need to interact, and you find yourself doing different things depending on which objects are being combined.

Note that multiple dispatch isn't the same thing as overloaded functions (in C++ or Java). In those languages, you can define two or more methods with the same name but different parameter lists, and the compiler works out which one to call based on the nominal types of the arguments you specify. In other words, the compiler analyzes the argument type information, selects the corresponding target method, and hardcodes a call to it. That means that if you then call the overloaded method with a set of arguments belonging to derived classes, you still invoke the method that handles the original base class arguments.

In multiple dispatch, on the other hand, the method is always chosen polymorphically, by examining the actual run-time types of the objects you passed as arguments, not the compile-time types of the pointers or references through which those arguments were passed. Hence, if you pass derived objects as arguments, you get the method that handles derived objects.



Subsecciones
Casiano Rodríguez León
2010-04-30