多重派发多个参数

bar*_*-md 5 java oop dynamic parameter-passing dispatch

是否有一种优雅的方法来获取具有(单个)动态调度的OO语言中具有2个参数(或更多参数)的方法的多个调度?

可能的问题示例:

这是一个受Java启发的示例。(问题与语言无关!)

// Visitable elements
abstract class Operand {
}
class Integer extends Operand {
    int value;
    public int getValue() {
        return value;
    }
}
class Matrix extends Operand {
    int[][] value;
    public int[][] getValue() {
        return value;
    }
}

// Visitors
abstract class Operator {
    // Binary operator
    public Operand eval(Operand a, Operand b) {
        return null; // unknown operation
    }
}
class Addition extends Operator {
    public Operand eval(Integer a, Integer b) {
        return new Integer(a.getValue() + b.getValue());
    }
}
class Multiplication extends Operator {
    public Operand eval(Integer a, Integer b) {
        return new Integer(a.getValue() * b.getValue());
    }
    // you can multiply an integer with a matrix
    public Operand eval(Integer a, Matrix b) {
        return new Matrix();
    }
}
Run Code Online (Sandbox Code Playgroud)

我有很多运算符和操作数具体类型,但仅通过它们的抽象类型来引用它们:

Operand a = new Integer()
Operand b = new Matrix();
Operand result;
Operator mul = new Multiplication();
result = mul.eval(a, b); // Here I want Multiplication::eval(Integer, Matrix) to be called
Run Code Online (Sandbox Code Playgroud)

Edw*_*rzo 1

我不确定我是否会回答你的问题,但我希望我能为讨论添加一些内容。我稍后会尝试提供一个更笼统的答案,但在本篇中我将只关注上面的示例。

问题中给出的示例的问题在于它基于算术运算,这使得它本质上很复杂,因为给定运算符的实现会根据其操作数的类型而变化。

我认为这个问题有点模糊了问题,例如我们可以花时间尝试使您的示例正常工作,而不是处理多个调度问题。

让代码正常工作的一种方法是从不同的角度思考。不是定义一个称为Operator我们可以做的抽象概念,而是认识操作数的固有性质,即它们必须提供可能影响它们的每个可能操作的实现。用面向对象的术语来说,每个操作数都包含一堆可以影响它们的操作。

因此,假设我有一个Operand这样的接口,它定义了操作数支持的每个可能的操作。请注意,我不仅为每个可能的已知操作数定义了一种方法方差,而且还为另一个未知操作数的一般情况定义了一种方法:

interface Operand {
    Operand neg();
    Operand add(Int that);
    Operand add(Decimal that);
    Operand add(Operand that);
    Operand mult(Int that);
    Operand mult(Decimal that);
    Operand mult(Operand that);
    Operand sub(Int that);
    Operand sub(Decimal that);
    Operand sub(Operand that);
}
Run Code Online (Sandbox Code Playgroud)

现在考虑我们有两个实现:IntDecimal(为了简单起见,我将使用十进制而不是示例的矩阵)。

class Int implements Operand {
    final int value;
    Int(int value) { this.value = value; }
    public Int neg(){ return new Int(-this.value); }
    public Int add(Int that) { return new Int(this.value + that.value); }
    public Decimal add(Decimal that) { return new Decimal(this.value + that.value); }
    public Operand add(Operand that) { return that.add(this); } //!
    public Int mult(Int that) { return new Int(this.value * that.value); }
    public Decimal mult(Decimal that) { return new Decimal(this.value * that.value); }
    public Operand mult(Operand that) { return that.mult(this); } //!
    public Int sub(Int that) { return new Int(this.value - that.value); }
    public Decimal sub(Decimal that) { return new Decimal(this.value - that.value); }
    public Operand sub(Operand that) { return that.neg().add(this); } //!
    public String toString() { return String.valueOf(this.value); }
}

class Decimal implements Operand {
    final double value;
    Decimal(double value) { this.value = value; }
    public Decimal neg(){ return new Decimal(-this.value); }
    public Decimal add(Int that) { return new Decimal(this.value + that.value); }
    public Decimal add(Decimal that) { return new Decimal(this.value + that.value); }
    public Operand add(Operand that) { return that.add(this); } //!
    public Decimal mult(Int that) { return new Decimal(this.value * that.value); }
    public Decimal mult(Decimal that) { return new Decimal(this.value * that.value); }
    public Operand mult(Operand that) { return that.mult(this); } //!
    public Decimal sub(Int that) { return new Decimal(this.value - that.value); }
    public Decimal sub(Decimal that) { return new Decimal(this.value - that.value); }
    public Operand sub(Operand that) { return that.neg().add(this); } //!
    public String toString() { return String.valueOf(this.value); }
}
Run Code Online (Sandbox Code Playgroud)

然后我可以这样做:

Operand a = new Int(10);
Operand b = new Int(10);
Operand c = new Decimal(10.0);
Operand d  = new Int(20);

Operand x = a.mult(b).mult(c).mult(d);
Operand y = b.mult(a);

System.out.println(x); //yields 20000.0
System.out.println(y); //yields 100

Operand m = new Int(1);
Operand n = new Int(9);

Operand q = m.sub(n); 
Operand t = n.sub(m); 

System.out.println(q); //yields -8
System.out.println(t); //yeilds 8
Run Code Online (Sandbox Code Playgroud)

这里的关键点是:

  • 每个操作数实现的工作方式都与访问者模式类似,从某种意义上说,每个操作数实现都包含一个针对您可以获得的每种可能组合的调度函数。
  • 棘手的部分是作用于任何 的方法Operand。这是我们利用访问者调度能力的地方,因为我们知道 的确切类型this,但不知道 的确切类型that,所以我们通过做强制调度that.method(this),问题就解决了!
  • 然而,由于我们颠倒了求值的顺序,因此对于不可交换的算术运算(例如减法)存在问题。这就是为什么我用加法代替减法(即 1-9 等于 1 + -9)。因为加法是可交换的。

现在你就得到了它。现在我找到了特定示例的解决方案,但是我还没有为您最初遇到的多重调度问题提供一个好的解决方案。这就是为什么我说这个例子不够好。

也许您可以提供一个更好的示例,例如维基百科页面中的Multiple Dispatch

然而,我认为解决方案可能永远是,将问题减少为一系列单一调度,并使用某种访问者模式来解决,就像我所做的那样。如果我有时间,我会稍后尝试给出更一般的答案,而不仅仅是这个具体的例子。

但希望这篇文章有助于促进进一步的讨论,幸运的是,它朝着实际答案的方向迈出了一步。