2016-11-02 5 views
4

Gibt es eine elegante Möglichkeit, Mehrfachversand für Methoden mit 2 Parametern (oder mehr) in OO-Sprachen mit (single) dynamischem Versand zu erhalten?Mehrfachversand für mehrere Argumente

Beispiel für ein mögliches Problem:

Dies ist ein Java-inspirierten Beispiel. (das Problem nicht die Sprache verwandt ist!)

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

ich viele Betreiber und Operanden- konkreten Typen haben aber nur, um sie durch ihre abstrakte Typen verweisen:

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

Wird Ihr Beispiel even kompilieren? (Anzahl an Operand-Konvertierungen) und könnten Sie ein bisschen mehr klären, was das Problem ist? Es ist eher unklar, was Sie gerade suchen – n247s

+0

Ich habe nicht die Konstruktoren geschrieben, so dass es nicht kompiliert, es ist nur ein Beispiel. Ich möchte Multiplication :: eval (Integer, Matrix) aufrufen, wenn das aufgerufene Objekt den statischen Typ Operator und Laufzeittyp Multiplikation hat und seine Argumente statischen Typ Operand und Laufzeittypen Integer und Matrix haben. Es gibt ein Muster dafür, wenn die eval-Methode nur ein Argument hat, heißt es multiples dispatch. Ich möchte das gleiche für 2 (oder mehr) Argumente. – biowep

+1

Sobald Sie "Operator mul;" definieren, sind Sie auf den durch "Operator" definierten Vertrag beschränkt. Wenn Sie also die Methode 'eval (Integer, Matrix)' in der Klasse 'Operator' (oder einem ihrer Eltern) nicht definieren, können Sie sie nicht aufrufen. Anstatt es vom Typ "Operator" zu deklarieren, können Sie es in "Multiplikation" ändern. – nickb

Antwort

1

Ich bin nicht sicher, dass ich antworten Ihre Frage, aber ich hoffe, ich kann ein wenig zur Diskussion beitragen. Ich werde versuchen, später mit einer allgemeineren Antwort zurückzukommen, aber in diesem werde ich mich nur auf Ihr Beispiel oben konzentrieren.

Das Problem mit dem Beispiel in der Frage ist, dass es auf arithmetischen Operationen basiert und dass es inhärent komplex ist, weil sich die Implementierung eines gegebenen Operators abhängig von den Typen seiner Operanden ändert.

Ich denke, dass das Problem die Frage ein wenig verdeckt, z. Wir könnten Zeit damit verbringen, Ihr Beispiel zum Laufen zu bringen, anstatt sich mit mehreren Versandproblemen zu befassen.

Eine Möglichkeit, Ihren Code zum Laufen zu bringen, wäre, von einem anderen Standpunkt aus zu denken. Anstatt eine Abstraktion namens Operator zu definieren, können wir die inhärente Natur der Operanden erkennen, nämlich dass sie eine Implementierung jeder möglichen Operation bereitstellen müssen, die sie beeinflussen kann. In objektorientierten Begriffen packt jeder Operand eine Reihe von Aktionen, die sie beeinflussen können.

Und stellen Sie sich vor, ich habe eine Schnittstelle Operand wie diese, die jede mögliche Operation definiert, die ein Operand unterstützt. Beachten Sie, dass ich nicht nur pro jeder möglichen bekannten Operanden eine Methode Varianz definiert, sondern auch ein Verfahren für einen allgemeinen Fall eines anderen unbekannten Operanden:

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

Dann betrachten wir jetzt, dass wir zwei Implementierungen dieser hatte: Int und Decimal (I wird zur Vereinfachung statt der Matrix Ihres Beispiels dezimal verwendet).

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

Dann kann ich dies tun:

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 

Die wichtigsten Punkte sind hier:

  • Jeder Operand Implementierung arbeitet in ähnlicher Weise zu einem Besuchermuster, in dem Sinne, dass jeder Operandenimplementierung enthält eine Dispatch-Funktion für jede mögliche Kombination, die Sie erhalten können.
  • Der schwierige Teil ist die Methode, die auf alle Operand wirkt. Dies ist der Ort, an dem wir die Besucher-Sendeleistung ausnutzen, da wir den genauen Typ von this kennen, aber nicht den genauen Typ von that, also erzwingen wir den Versand per that.method(this) und Problem gelöst!
  • Da wir jedoch die Reihenfolge der Auswertung invertieren, hat dies ein Problem mit arithmetischen Operationen, die nicht kommutativ sind, wie die Subtraktion. Deshalb subtrahiere ich stattdessen Addition (d. H.1-9 entspricht 1 + -9). Da ist Addition kommutativ.

Und da hast du es. Jetzt habe ich eine Lösung für das spezifische Beispiel gefunden, aber ich habe keine gute Lösung für das Problem mit dem Mehrfachversand bereitgestellt, das Sie ursprünglich hatten. Deshalb sagte ich, das Beispiel sei nicht gut genug.

Vielleicht könnten Sie ein besseres Beispiel liefern, wie das auf der Wikipedia-Seite für Multiple Dispatch.

Aber ich denke, die Lösung wird wahrscheinlich immer sein, reduzieren Sie das Problem auf eine Reihe von einzelnen Sendungen gelöst mit einer Art von Besucher Muster wie die, die ich gemacht habe. Wenn ich Zeit habe, werde ich versuchen, es später zu einer allgemeineren Antwort und nicht nur diesem spezifischen Beispiel zu geben.

Aber hoffentlich hilft dieser Beitrag, weitere Diskussion zu fördern und mit etwas Glück ist es ein Schritt in Richtung der tatsächlichen Antwort.

1

Onkel Bob tat dies:

// 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

ich seit den beiden oben genannten Antworten auf diese hinzuzufügen entschieden etwas unvollständig. Ich war selbst neugierig auf dieses Thema, konnte aber keine Antwort finden und musste meine eigene Analyse machen. Im Allgemeinen gibt es zwei Ansätze, die verwendet werden können, um Multi-Methoden in Sprachen wie C++ oder Java zu implementieren, die sowohl den einzelnen dynamischen Versand als auch eine Art Typ- oder Laufzeittyp-Identifikation unterstützen.

Für den Doppelversandfall ist das Besuchermuster am häufigsten (für Arg1-> foo (Arg2)) und ich nehme in den meisten Fällen vor, RTTI und Schalter oder if-Anweisungen zu verwenden. Man kann den Besucher-Ansatz auch auf den n-Fall verallgemeinern, zB durch Arg1-> foo (Arg2, Arg3..ArgN), indem eine Reihe von einzelnen Nachrichten verkettet wird, die Methoden in einer baumartigen Struktur aufrufen, die sich vom Typ der Argumente bis zu einigen unterscheiden k und spalte die Anzahl der Wege des k + 1 Arguments. Zum Beispiel für einen einfachen Fall von Triple-Versand und nur zwei Instanzen jeden Typs:

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

Obwohl der Ansatz verallgemeinert das Problem liegen auf der Hand. Für jede Endpunktfunktion müssen im schlechtesten Fall n-1 Dispatch-Funktionen geschrieben werden.

Man kann etwas ähnliches mit Laufzeittypidentifikation über den instanceOf Betreiber erreichen:

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

Die zweite Lösung wahrscheinlich weiter vereinfacht Reflexion verwendet werden kann.

Verwandte Themen