多个参数的多个调度

时间:2016-11-02 17:41:37

标签: 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();
    }
}

我有许多操作符和操作数具体类型,但只能通过它们的抽象类型引用它们:

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

3 个答案:

答案 0 :(得分: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);
}

然后现在考虑我们有两个这样的实现: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)。由于加法是可交换的。

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

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

但是,我认为解决方案可能永远都是,将问题减少到一系列单一调度,这些调度是通过我所做的某种访问者模式解决的。如果我有时间,我会尝试稍后给出一个更普遍的答案,而不仅仅是这个具体的例子。

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

答案 1 :(得分:1)

鲍勃叔叔这样做了:

 // 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);
            }
        }

答案 2 :(得分:1)

我决定加上这个,因为上面两个答案有点不完整。我对这个问题很好奇,但找不到答案,所以不得不做我自己的分析。通常,有两种方法可用于在C ++或Java等语言中实现多方法,这些方法既支持单动态分派,又支持类型或运行时类型识别。

对于双重调度情况,访问者模式是最常见的(对于Arg1-> foo(Arg2))并且我在大多数情况下收集优先使用RTTI和switch或if语句。也可以通过链接一系列单个调度来概括访问者对n情况的方法,即Arg1-> foo(Arg2,Arg3..ArgN),这些调度调用树状结构中的方法,该结构区分参数的类型,直到一些k和分裂k + 1参数的方式的数量。例如,对于三重调度的简单情况,每种类型只有两个实例:

_ = URLSession.shared.dataTask (with: url_request) 
{(data, response, error) in
}.resume ()

print ("Hello world")

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

可以通过instanceOf运算符实现类似于运行时类型识别的东西:

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);
    }
}

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