2016-11-02 11 views
4

(1つの)ダイナミックディスパッチを持つ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 
+0

例evemはコンパイルされますか? (数値をオペランドに変換)。問題の内容をもう少し明確にすることができますか? – n247s

+0

私はコンストラクタを書きませんでしたので、コンパイルされません。これは単なる例です。 呼び出すオブジェクトが静的型の演算子と実行時型の乗算を持ち、その引数が静的型のオペランドと実行時型のIntegerとMatrixを持つ場合、Multiplication :: eval(Integer、Matrix)を呼び出すことができます。このためのパターンが存在し、evalメソッドに引数が1つしかない場合は、複数ディスパッチと呼ばれます。私は2つ(またはそれ以上)の議論について同じことを望む。 – biowep

+1

'Operator mul;'を定義するとすぐに、 'Operator'で定義された契約に制限されます。したがって、 'Operator'クラス(またはその親の1つ)で' eval(Integer、Matrix) 'メソッドを定義しない限り、それを呼び出すことはできません。これを 'Operator'型と宣言するのではなく、' Multiplication'に変更することができます。 – nickb

答えて

1

は私が答えることを確認していませんあなたの質問は、私は議論に少し追加することを願っています。私は後でもっと一般的な答えを返そうとしますが、この中で私は上記のあなたの例にのみ焦点を当てます。

問題の例の問題は、算術演算に基づいており、与えられた演算子の実装がそのオペランドの型によって変わるため、本質的に複雑になります。

私はこの問題が少し質問をあいまいにすると思います。複数のディスパッチの問題を扱う代わりに、サンプルを作成するために時間を費やすことができます。

コードを動作させる1つの方法は、異なる視点から考えることです。 Operatorという抽象概念を定義する代わりに、オペランドの本来の性質を認識することができます。すなわち、オペランドに影響を与える可能性があるすべての操作の実装を提供する必要があります。オブジェクト指向の用語では、すべてのオペランドはそれらに影響を与える一連のアクションをパックします。

それで、このようなインターフェイスOperandがあり、オペランドがサポートするすべての可能な操作を定義しているとします。私はすべての可能な既知のオペランドごとに1つの方法の分散を定義していないことに注意してくださいだけでなく、他の未知のオペランドの一般的なケースのための1つの方法:IntDecimal(I:今、その後

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

は、私たちがこの2つの実装を持っていたことを考えます単純化のためにあなたの例の行列の代わりに小数点を使用します)。

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のWikipediaページのようなより良い例を提供することができます。

しかし、私は解決策はおそらく、私がしたような訪問者のパターンの何らかの種類で解決された一連の単一のディスパッチに問題を減らすと思います。私に時間があれば、この具体的な例だけでなく、より一般的な答えに、後でそれを打ち込むようにします。

しかし、この投稿は今後の議論を促進するのに役立ち、運があれば実際の回答の方向性を示します。

1

叔父ボブこのでした:私は上記の2つの答えがやや不完全であるため、これに追加することを決定した

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

を。私は自分自身でこの問題について興味があったが、答えを見つけることができなかったので、自分の分析をしなければならなかった。一般に、C++やJavaのような言語で複数のメソッドを実装するには、単一の動的ディスパッチと型やランタイム型の識別の両方をサポートする2つの方法があります。

ダブルディスパッチの場合、ビジターパターンが最も一般的です(Arg1-> foo(Arg2))。ほとんどの場合、RTTIとスイッチまたはif文を使用するよりも優先されます。引数の型をいくつかに区別するツリー構造のメソッドを呼び出す一連の単一ディスパッチを連鎖させることで、Arg1-> foo(Arg2、Arg3..ArgN)というnの場合の訪問者のアプローチを一般化することもできますkとk + 1引数のウェイ数を分割します。たとえば、トリプル派遣し、各タイプの唯一の2つのインスタンスの単純なケースのために:

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ディスパッチ関数を書かなければならない。第二の溶液は、おそらくさらに反射を使用して簡素化することができる

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

一つはinstanceof演算子を介して実行時型識別と同様のものを達成することができます。

関連する問題