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-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
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
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.