【问题标题】:Multiple dispatch for multiple arguments多个参数的多次分派
【发布时间】:2016-11-02 17:41:37
【问题描述】:

有没有一种优雅的方式来为具有(单个)动态调度的 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();
    }
}

我有很多 Operator 和 Operand 的具体类型,但只能通过它们的抽象类型来引用它们:

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

【问题讨论】:

  • 你的例子是否可以编译? (数字到操作数的转换)。你能澄清一下问题是什么吗?现在还不清楚您在寻找什么
  • 我没有写构造函数所以它不能编译,这只是一个例子。当被调用的对象具有静态类型运算符和运行时类型乘法并且其参数具有静态类型操作数和运行时类型整数和矩阵时,我想要调用 Multiplication::eval(Integer, Matrix)。为此存在一种模式,当 eval 方法只有一个参数时,称为多重分派。对于 2 个(或更多)参数,我想要相同的参数。
  • 一旦你定义了Operator mul;,你就受限于Operator定义的合约。因此,除非您在 Operator 类(或其父类之一)中定义 eval(Integer, Matrix) 方法,否则您将无法调用它。您可以将其更改为Multiplication,而不是将其声明为Operator 类型。

标签: java oop dynamic parameter-passing dispatch


【解决方案1】:

我决定补充这一点,因为上述两个答案有些不完整。我自己对这个问题很好奇,但找不到答案,所以不得不自己分析。一般来说,有两种方法可用于在 C++ 或 Java 等语言中实现多方法,这些语言既支持单一动态调度,又支持 typeof 或运行时类型识别等功能。

对于双分派情况,访问者模式是最常见的(对于 Arg1->foo(Arg2) ),我认为在大多数情况下,首选使用 RTTI 和开关或 if 语句。也可以通过链接一系列单个调度来将访问者方法推广到 n 案例,即 Arg1->foo(Arg2,Arg3..ArgN) k 并拆分 k+1 参数的方式数。例如对于一个简单的三重分派案例,每种类型只有两个实例:

interface T1 {
    public void f(T2 arg2, T3 arg3);
}

interface T2 {
    public void gA(A a, T3 arg3)
    public void gB(B b, T3 arg3)
}

interface T3 {
    public void hAC(A a,C c);
    public void hAD(A a,D d);
    public void hBC(B b,C c);
    public void hBD(B b,D d);
}

class A implements T1 {
    public void f(T2 arg2, T3 arg3) {
        arg2->gA(this,arg3);
    }
}

class B implements T1 {
    public void f(T2 arg2, T3 arg3) {
        arg2->gB(this,arg3);
    }
}

class C implements T2 {
    public void gA(A a,T arg3) {
        arg3->hAC(a, this);
    }

    public void gB(B b,T arg3) {
        arg3->hBC(b, this);
    }
}

class D implements T2 {
    public void gA(A a,T arg3) {
        arg3->hAD(a, this);
    }

    public void gB(B b,T arg3) {
        arg3->hBD(b, this);
    }
}

class E implements T3 {
    public void  hAC(A a,C c) {
        System.out.println("ACE");
    }
    public void  hAD(A a,D d) {
        System.out.println("ADE");
    }
    public void  hBC(B b,C c) {
        System.out.println("BCE");
    }
    public void  hBD(B b,D d) {
        System.out.println("BDE");
    }

}

class F implements T3 {
    public void  hAC(A a,C c) {
        System.out.println("ACF");
    }
    public void  hAD(A a,D d) {
        System.out.println("ADF");
    }
    public void  hBC(B b,C c) {
        System.out.println("BCF");
    }
    public void  hBD(B b,D d) {
        System.out.println("BDF");
    }

}

public class Test {
    public static void main(String[] args) {
        A a = new A();
        C c = new C();
        E e = new E();

        a.f(c,e);
    }
}

虽然该方法概括了问题,但非常明显。在最坏的情况下,对于每个端点函数,必须编写 n-1 个调度函数。

可以通过 instanceOf 运算符实现与运行时类型识别类似的功能:

class Functions
{
    static void f(A a,C c,E e) {
        System.out.println("ACE");
    }
    static void f(A a,C c,F f) {
        System.out.println("ACF");
    }
    static void f(A a,D d,E e) {
        System.out.println("ADE");
    }
    static void f(A a,D d,F f) {
        System.out.println("ADF");
    }
    static void f(B b,C c,E e) {
        System.out.println("BCE");
    }
    static void f(B b,C c,F f) {
        System.out.println("BCF");
    }
    static void f(B b,D d,E e) {
        System.out.println("BDE");
    }
    static void F(B b,D d,F f) {
        System.out.println("BDF");
    }

    static void dispatch(T1 t1, T2 t2, T3 t3) {
        if( t1 instanceOf A)
        {
            if(t2 instance of C) {
                if(t3 instance of E) {
                    Function.F( (A)t1, (C)t2, (E)t3 );
                }
                else if(t3 instanceOf F) {
                    Function.F( (A)t1, (C)t2, (F)t3 );
                }
            }
            else if(t2 instance of D) {
                if(t3 instance of E) {
                    Function.F( (A)t1, (D)t2, (E)t3 );
                }
                else if(t3 instanceOf F) {
                    Function.F( (A)t1, (D)t2, (F)t3 );
                }
            }
        }
        else if(t1 instanceOf B) {
            if(t2 instance of C) {
                if(t3 instance of E) {
                    Function.F( (B)t1, (C)t2, (E)t3 );
                }
                else if(t3 instanceOf F) {
                    Function.F( (B)t1, (C)t2, (F)t3 );
                }
            }
            else if(t2 instance of D) {
                if(t3 instance of E) {
                    Function.F( (B)t1, (D)t2, (E)t3 );
                }
                else if(t3 instanceOf F) {
                    Function.F( (B)t1, (D)t2, (F)t3 );
                }
            }
        }
    }
}

第二种解决方案可能可以使用反射进一步简化。

【讨论】:

    【解决方案2】:

    我不确定我会回答你的问题,但我希望我能在讨论中添加一些内容。稍后我将尝试提供更一般的答案,但在此我将仅关注您上面的示例。

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

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

    让您的代码工作的一种方法是从不同的角度进行思考。除了定义一个称为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);
    }
    

    那么现在考虑我们有两个实现: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); }
    }
    

    那么我可以这样做:

    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
    

    这里的重点是:

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

    你有它。现在我找到了具体示例的解决方案,但我没有为您最初遇到的多分派问题提供一个好的解决方案。这就是为什么我说这个例子不够好。

    也许您可以提供一个更好的示例,例如 Wikipedia 页面中Multiple Dispatch 的示例。

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

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

    【讨论】:

      【解决方案3】:

      鲍勃叔叔这样做了:

       // visitor with triple dispatch. from a post to comp.object by robert martin http://www.oma.com
          /*
          In this case, we are actually using a triple dispatch, because we have two
          types to resolve.  The first dispatch is the virtual Collides function which
          resolves the type of the object upon which Collides is called.  The second
          dispatch is the virtual Accept function which resolves the type of the
          object passed into Collides.  Now that we know the type of both objects, we
          can call the appropriate global function to calculate the collision.  This
          is done by the third and final dispatch to the Visit function.
          */
          interface AbstractShape
              {
              boolean Collides(final AbstractShape shape);
              void Accept(ShapeVisitor visitor);
              }
          interface ShapeVisitor
              {
              abstract public void Visit(Rectangle rectangle);
              abstract public void Visit(Triangle triangle);
              }
          class Rectangle implements AbstractShape
              {
              public boolean Collides(final AbstractShape shape)
                  {
                  RectangleVisitor visitor=new RectangleVisitor(this);
                  shape.Accept(visitor);
                  return visitor.result();
                  }
              public void Accept(ShapeVisitor visitor)
                  { visitor.Visit(this); } // visit Rectangle
              }
          class Triangle implements AbstractShape
              {
              public boolean Collides(final AbstractShape shape)
                  {
                  TriangleVisitor visitor=new TriangleVisitor(this);
                  shape.Accept(visitor);
                  return visitor.result();
                  }
              public void Accept(ShapeVisitor visitor)
                  { visitor.Visit(this); } // visit Triangle
              }
      
          class collision
              { // first dispatch
              static boolean Collides(final Triangle t,final Triangle t2) { return true; }
              static boolean Collides(final Rectangle r,final Triangle t) { return true; }
              static boolean Collides(final Rectangle r,final Rectangle r2) { return true; }
              }
          // visitors.
          class TriangleVisitor implements ShapeVisitor
              {
              TriangleVisitor(final Triangle triangle)
                  { this.triangle=triangle; }
              public void Visit(Rectangle rectangle)
                  { result=collision.Collides(rectangle,triangle); }
              public void Visit(Triangle triangle)
                  { result=collision.Collides(triangle,this.triangle); }
              boolean result() {return result; }
              private boolean result=false;
              private final Triangle triangle;
              }
          class RectangleVisitor implements ShapeVisitor
              {
              RectangleVisitor(final Rectangle rectangle)
                  { this.rectangle=rectangle; }
              public void Visit(Rectangle rectangle)
                  { result=collision.Collides(rectangle,this.rectangle); }
              public void Visit(Triangle triangle)
                  { result=collision.Collides(rectangle,triangle); }
              boolean result() {return result; }
              private boolean result=false;
              private final Rectangle rectangle;
              }
          public class MartinsVisitor
              {
              public static void main (String[] args)
                  {
                  Rectangle rectangle=new Rectangle();
                  ShapeVisitor visitor=new RectangleVisitor(rectangle);
                  AbstractShape shape=new Triangle();
                  shape.Accept(visitor);
                  }
              }
      

      【讨论】:

        猜你喜欢
        • 2019-03-26
        • 2017-10-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-03-18
        • 2021-10-02
        相关资源
        最近更新 更多