Множественная диспетчеризация (Multiple dispatching)


Когда работаете со многими типами, которые взаимодействуют друг с другом, программа может стать грязной. В качестве примера можно привести систему, анализирующую и выполняющую математические выражения. Возможно, вы захотите написать Number + Number, Number * Number и т. п. где Number - базовый класс для ряда числовых объектов. Но когда вы пишите a + b и не знаете точный тип a и b, то как вы заставите их правильно взаимодействовать?

Ответ начинается с того, о чем вы, возможно, даже и не думали: Java выполняет только единую диспетчеризацию. То есть, если вы выполняете операцию над более чем одним объектом, тип которого не известен, Java может вызвать механизм динамического связывания только для одного из этих типов. Это не решает проблему, так что вы вынуждены определять некоторые типы вручную и воспроизводить свой собственный механизм динамического связывания.

Решение называется множественной диспетчеризацией. Помните, что полиморфизм может возникнуть только через вызов функций-методов, так что если вы хотите запустить механизм двойной диспетчеризации, должно быть два вызова функций-членов: первый определяет первый неизвестный тип, а второй вызов определяет второй неизвестный тип. С множественной диспетчеризацией вы должны иметь вызовы полиморфичных методов для определения каждого типа. Обычно, вы будете стараться получить такую конфигурацию, которая при единственном вызове метода производила более одного динамического вызова функции-члена и таким образом определяла более одного типа в процессе. Чтобы получить такой эффект, вам необходимо работать с более, чем одним вызовом полиморфичного метода: вам необходим один вызов для каждой диспетчеризации. Методы в следующем примере вызывают compete( ) и eval( ), в котором оба члена одинакового типа. (В этом случае будет только два диспетчера, что называется двойной диспетчеризацией). Если вы работаете с двумя разными взаимодействующими иерархиями типов, то вы будете иметь вызовы полиморфических методов для каждой иерархии.

Вот пример множественной диспетчеризации:

//: multipledispatch:PaperScissorsRock.java
// Demonstration of multiple dispatching.
package multipledispatch;

import java.util.*;

import junit.framework.*;

// An enumeration type:
class Outcome {
  
private String name;
  
  
private Outcome(String name) {
     
this.name = name;
  
}
  
  
public final static Outcome WIN = new Outcome("wins"), LOSE = new Outcome(
        
"loses"), DRAW = new Outcome("draws");
  
  
public String toString() {
     
return name;
  
}
}

interface Item {
  
Outcome compete(Item it);
  
   Outcome eval
(Paper p);
  
   Outcome eval
(Scissors s);
  
   Outcome eval
(Rock r);
}

class Paper implements Item {
  
public Outcome compete(Item it) {
     
return it.eval(this);
  
}
  
  
public Outcome eval(Paper p) {
     
return Outcome.DRAW;
  
}
  
  
public Outcome eval(Scissors s) {
     
return Outcome.WIN;
  
}
  
  
public Outcome eval(Rock r) {
     
return Outcome.LOSE;
  
}
  
  
public String toString() {
     
return "Paper";
  
}
}

class Scissors implements Item {
  
public Outcome compete(Item it) {
     
return it.eval(this);
  
}
  
  
public Outcome eval(Paper p) {
     
return Outcome.LOSE;
  
}
  
  
public Outcome eval(Scissors s) {
     
return Outcome.DRAW;
  
}
  
  
public Outcome eval(Rock r) {
     
return Outcome.WIN;
  
}
  
  
public String toString() {
     
return "Scissors";
  
}
}

class Rock implements Item {
  
public Outcome compete(Item it) {
     
return it.eval(this);
  
}
  
  
public Outcome eval(Paper p) {
     
return Outcome.WIN;
  
}
  
  
public Outcome eval(Scissors s) {
     
return Outcome.LOSE;
  
}
  
  
public Outcome eval(Rock r) {
     
return Outcome.DRAW;
  
}
  
  
public String toString() {
     
return "Rock";
  
}
}

class ItemGenerator {
  
private static Random rand = new Random();
  
  
public static Item newItem() {
     
switch (rand.nextInt(3)) {
     
default:
     
case 0:
        
return new Scissors();
     
case 1:
        
return new Paper();
     
case 2:
        
return new Rock();
     
}
   }
}

class Compete {
  
public static void match(Item a, Item b) {
     
System.out.println(a + " " + a.compete(b) + " vs. " + b);
  
}
}

public class PaperScissorsRock extends TestCase {
  
static int SIZE = 20;
  
  
public void test() {
     
for (int i = 0; i < SIZE; i++)
        
Compete.match(ItemGenerator.newItem(), ItemGenerator.newItem());
  
}
  
  
public static void main(String args[]) {
     
junit.textui.TestRunner.run(PaperScissorsRock.class);
  
}
}
// /:~