2016-11-02 4 views
4

Есть ли элегантный способ получить многократную отправку для методов с двумя параметрами (или даже более) на языках OO с (единой) динамической отправкой?Многократная отправка для нескольких аргументов

Пример возможной проблемы:

Это 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

Скомпилирован ли ваш пример? (Преобразование числа в операнд). и не могли бы вы уточнить немного больше, что проблема? Его довольно неясно сейчас, что вы ищете – n247s

+0

Я не писал конструкторы, поэтому он не компилируется, это просто пример. Я хочу вызвать Multiplication :: eval (Integer, Matrix), когда вызываемый объект имеет статический тип. Оператор и тип времени выполнения. Умножение и его аргументы имеют тип статического типа. Операнд и типы выполнения Integer и Matrix. Существует шаблон для этого, когда метод eval имеет только один аргумент, он называется множественной отправкой. Я хочу то же самое для 2 (или более) аргументов. – biowep

+1

Как только вы определяете 'Operator mul;', вы ограничены контрактом, определяемым 'Operator'. Поэтому, если вы не определите метод 'eval (Integer, Matrix)' в классе 'Operator' (или один из его родителей), вы не сможете его вызывать. Вместо того, чтобы объявлять его типом 'Operator', вы можете изменить его на' Multiplication'. – nickb

ответ

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

Тогда сейчас считает, что у нас было две реализаций этого: Int и Decimal (I для простоты будет использовать десятичную матрицу вместо матрицы вашего примера).

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 содержит функцию отправки для каждой возможной комбинации, которую вы можете получить.
  • Трудная часть - это метод, действующий на любом Operand. Это место, где мы используем мощность отправки посетителя, потому что мы знаем точный тип this, но не точный тип that, поэтому мы вынуждаем отправку, делая that.method(this), и проблема решена!
  • Однако, поскольку мы инвертируем порядок оценки, это имеет проблему с арифметическими операциями, которые не являются коммутативными, как вычитание. Вот почему я делаю вычитание с помощью добавления (т. Е.1-9 соответствует 1 + -9). Так как сложение является коммутативным.

И у вас оно есть. Теперь я нашел решение для конкретного примера, но я не предоставил хорошее решение проблемы с несколькими отправками, которые вы изначально имели. Вот почему я сказал, что пример недостаточно хорош.

Возможно, вы могли бы предоставить лучший пример, например, тот, который находится на странице Википедии для Multiple Dispatch.

Однако, я думаю, что решение, вероятно, всегда будет, уменьшите проблему до серии отдельных рассылок, разрешенных каким-то шаблоном посетителя, как тот, который я сделал. Если у меня будет время, я постараюсь дать ему ответ позже на более общий ответ, а не только на этот конкретный пример.

Но, надеюсь, этот пост способствует дальнейшему обсуждению, и, к счастью, это шаг в направлении реального ответа.

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

я решил добавить к этому, так как выше двух ответов несколько неполными. Я сам был любопытен в этой проблеме, но не смог найти ответа, поэтому должен был провести мой собственный анализ. В общем, существует два подхода, которые могут быть использованы для реализации мульти-методов на таких языках, как C++ или Java, которые поддерживают как единую динамическую рассылку, так и типа типа type или идентификатора типа времени исполнения.

Для случая двойной отправки шаблон посетителя является наиболее распространенным (для 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); 
       } 
      } 
     } 
    } 
} 

Второе решение, вероятно, может быть еще более упрощен с помощью отражения.

Смежные вопросы