多路分发就是指在调用a.plus(b),a和b都不知道确切类型,也能让他们正常交互。
如果想使用两路分发,那么必须有两个方法调用,第一个方法调用决定第一个未知类型,第二个方法调用决定第二个未知类型。要利用多路分发,程序员必须为每一个类型提供给一个实际的方法调用。一般而言,程序员需要设定好某种配置,以便一个方法调用能够引出更多的方法调用,从而能在这个过程中处理多个类型。
下面是个“石头 剪刀 布”(RoShamBo)游戏的例子 (from: thinking in java):
-
public enum Outcome { WIN, LOSE, DRAW } ///:~
-
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 DRAW;
-
}
-
public Outcome eval(Scissors s) {
-
return WIN;
-
}
-
public Outcome eval(Rock r) {
-
return 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 LOSE;
-
}
-
public Outcome eval(Scissors s) {
-
return DRAW;
-
}
-
public Outcome eval(Rock r) {
-
return 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 WIN;
-
}
-
public Outcome eval(Scissors s) {
-
return LOSE;
-
}
-
public Outcome eval(Rock r) {
-
return DRAW;
-
}
-
public String toString() {
-
return "Rock";
-
}
-
}
-
public class RoShamBo1 {
-
static final int SIZE = 20;
-
private static Random rand = new Random(47);
-
public static Item newItem() {
-
switch (rand.nextInt(3)) {
-
default:
-
case 0:
-
return new Scissors();
-
case 1:
-
return new Paper();
-
case 2:
-
return new Rock();
-
}
-
}
-
public static void match(Item a, Item b) {
-
System.out.println(a + " vs. " + b + ": " + a.compete(b));
-
}
-
public static void main(String[] args) {
-
for (int i = 0; i < SIZE; i++)
-
match(newItem(), newItem());
-
}
-
}
RoshamBol.match()有2个item参数,通关过Item.compete()方法开始2路分发,要判定a的类型,分发机制会在a的实际类型的compete()内部起到分发作用。compete()方法通关过eval()来为另一个类型实现第二次分发, 将自身(this)作为参数调用eval(),能够调用重载过的eval()方法,这能够保留第一次分发的类型信息,第二次分发完成时,就能知道两个Item对象的具体类型了。
使用EnumMap分发:
-
enum RoShamBo5 implements Competitor<RoShamBo5> {
-
PAPER, SCISSORS, ROCK;
-
static EnumMap<RoShamBo5, EnumMap<RoShamBo5, Outcome>> table = new EnumMap<RoShamBo5, EnumMap<RoShamBo5, Outcome>>(
-
RoShamBo5.class);
-
static {
-
for (RoShamBo5 it : RoShamBo5.values())
-
table.put(it, new EnumMap<RoShamBo5, Outcome>(RoShamBo5.class));
-
initRow(PAPER, DRAW, LOSE, WIN);
-
initRow(SCISSORS, WIN, DRAW, LOSE);
-
initRow(ROCK, LOSE, WIN, DRAW);
-
}
-
static void initRow(RoShamBo5 it, Outcome vPAPER, Outcome vSCISSORS,
-
Outcome vROCK) {
-
EnumMap<RoShamBo5, Outcome> row = RoShamBo5.table.get(it);
-
row.put(RoShamBo5.PAPER, vPAPER);
-
row.put(RoShamBo5.SCISSORS, vSCISSORS);
-
row.put(RoShamBo5.ROCK, vROCK);
-
}
-
public Outcome compete(RoShamBo5 it) {
-
return table.get(this).get(it);
-
}
-
public static void main(String[] args) {
-
RoShamBo.play(RoShamBo5.class, 20);
-
}
-
}
-
public class RoShamBo {
-
public static <T extends Competitor<T>> void match(T a, T b) {
-
System.out.println(a + " vs. " + b + ": " + a.compete(b));
-
}
-
public static <T extends Enum<T> & Competitor<T>> void play(
-
Class<T> rsbClass, int size) {
-
for (int i = 0; i < size; i++)
-
match(Enums.random(rsbClass), Enums.random(rsbClass));
-
}
-
}