2016-11-02 1 views
4

Existe-t-il un moyen élégant d'obtenir une répartition multiple pour les méthodes avec 2 paramètres (ou même plus) dans les langages OO avec répartition dynamique (unique)?Envoi multiple pour plusieurs arguments

Exemple d'un problème possible:

Ceci est un exemple Java d'inspiration. (le problème est pas le langage lié!)

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

J'ai beaucoup opérateur et Opérande types concrets, mais seulement pour les référer par leurs types abstraits:

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

Est-ce que votre exemple evem compile? (Nombre de conversions opérandes). et pourriez-vous clarifier un peu plus quel est le problème? Ce n'est pas clair en ce moment de ce que vous cherchez – n247s

+0

Je n'ai pas écrit les constructeurs donc il ne compile pas, c'est juste un exemple. Ce que je veux, c'est appeler Multiplication :: eval (Integer, Matrix) lorsque l'objet appelé a le type statique Opérateur et type d'exécution Multiplication et ses arguments ont un type statique Types d'opérandes et d'exécution Entier et Matrix. Il existe un modèle pour cela, lorsque la méthode eval n'a qu'un argument, elle est appelée dispatch multiple. Je veux la même chose pour 2 (ou plus) arguments. – biowep

+1

Dès que vous définissez 'Operator mul;' vous êtes limité au contrat défini par 'Operator'. Donc, à moins de définir la méthode 'eval (Integer, Matrix)' dans la classe 'Operator' (ou l'un de ses parents), vous ne pourrez pas l'invoquer. Au lieu de déclarer qu'il est de type 'Operator', vous pouvez le changer en' Multiplication'. – nickb

Répondre

1

Je ne suis pas sûr que je répondrai votre question, mais j'espère pouvoir ajouter un peu à la discussion. Je vais essayer de revenir plus tard avec une réponse plus générale, mais dans celui-ci je vais me concentrer uniquement sur votre exemple ci-dessus.

Le problème avec l'exemple donné dans la question est qu'il est basé sur des opérations arithmétiques et cela le rend intrinsèquement complexe car l'implémentation d'un opérateur donné change en fonction des types de ses opérandes.

Je pense que le problème obscurcit un peu la question, par ex. nous pourrions passer du temps à essayer de faire fonctionner votre exemple au lieu de gérer plusieurs problèmes de répartition. Une façon de faire fonctionner votre code serait de penser d'un point de vue différent. Au lieu de définir une abstraction appelée Operator, nous pouvons reconnaître la nature inhérente des opérandes, à savoir qu'ils doivent fournir une implémentation de toutes les opérations possibles qui peuvent les affecter. En termes orientés objet, chaque opérande contient un tas d'actions qui peuvent les affecter.

Et donc, imaginez que j'ai une interface Operand comme ça, qui définit toutes les opérations possibles supportées par un opérande. Notez que non seulement je défini une méthode de variance par chaque possible opérande connu, mais aussi une méthode pour un cas général d'un autre opérande inconnu:

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

Alors maintenant considérer que nous avions deux implémentations de cela: Int et Decimal (I utilisera la décimale au lieu de la matrice de votre exemple pour plus de simplicité).

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

Je peux le faire:

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 

Les points clés sont:

  • Chaque implémentation opérandes fonctionne d'une manière similaire à un modèle de visiteur, dans le sens que tous les L'implémentation de l'opérande contient une fonction de répartition pour chaque combinaison possible que vous pouvez obtenir. La partie délicate est la méthode qui agit sur tout Operand. C'est l'endroit où nous exploitons la puissance d'expédition des visiteurs, car nous connaissons le type exact de this, mais pas le type exact de that, donc nous forçons l'envoi en faisant that.method(this) et le problème est résolu!
  • Cependant, puisque nous inversons l'ordre de l'évaluation, cela pose un problème avec les opérations arithmétiques qui ne sont pas commutatives, comme la soustraction. C'est pourquoi je fais la soustraction en utilisant l'addition à la place (c.-à-d.1-9 équivaut à 1 + -9). Puisque l'addition est commutative.

Et là vous l'avez. Maintenant j'ai un peu trouvé une solution à l'exemple spécifique, mais je n'ai pas fourni une bonne solution au problème de répartition multiple que vous aviez à l'origine. C'est pourquoi j'ai dit que l'exemple n'était pas assez bon.

Peut-être que vous pourriez fournir un meilleur exemple, comme celui de la page Wikipedia pour Multiple Dispatch.

Cependant, je pense que la solution sera probablement toujours, de réduire le problème à une série de messages simples résolus avec une sorte de modèle de visiteur comme celui que j'ai fait. Si j'ai le temps, je vais essayer de donner plus tard une réponse plus générale et pas seulement cet exemple précis.

Mais j'espère que ce message contribue à encourager la poursuite des discussions et avec de la chance, il est un pas dans la direction de la réponse réelle.

1

bob oncle a fait:

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

J'ai décidé d'ajouter à cela puisque les deux réponses ci-dessus sont un peu incomplètes. J'étais moi-même curieux de cette question mais je n'ai pas trouvé de réponse et j'ai dû faire ma propre analyse. En général, il existe deux approches qui peuvent être utilisées pour implémenter des méthodes multiples dans des langages tels que C++ ou Java qui supportent à la fois la répartition dynamique unique et quelque chose comme l'identification de type typeof ou runtime. Pour le cas de double expédition, le modèle de visiteur est le plus commun (pour Arg1-> foo (Arg2)) et je suppose que dans la plupart des cas, il est préférable d'utiliser RTTI et les commutateurs ou les instructions if. On peut aussi généraliser l'approche du visiteur au n cas par exemple Arg1-> foo (Arg2, Arg3..ArgN) en enchaînant une série de distributions simples qui appellent des méthodes dans une structure arborescente qui différencie sur le type des arguments jusqu'à certains k et diviser le nombre de façons de l'argument k + 1. Par exemple, pour un simple cas d'envoi triple et deux instances de chaque type:

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

Bien que l'approche généralise le problème sont tout à fait évident. Pour chaque fonction de point final, dans le pire des cas, il faut écrire n-1 fonctions de répartition.

On peut obtenir quelque chose de similaire avec l'identification du type d'exécution via l'opérateur 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); 
       } 
      } 
     } 
    } 
} 

La deuxième solution peut probablement être encore simplifiée en utilisant la réflexion.