Search code examples
javareflectionoverloadingvisitor-patternmultiple-dispatch

Invoking a method overloaded where all arguments implement the same interface


My starting point is the following:
- I have a method, transform, which I overloaded to behave differently depending on the type of arguments that are passed in (see transform(A a1, A a2) and transform(A a1, B b) in my example below)
- All these arguments implement the same interface, X

I would like to apply that transform method on various objects all implementing the X interface.

What I came up with was to implement transform(X x1, X x2), which checks for the instance of each object before applying the relevant variant of my transform.

Though it works, the code seems ugly and I am also concerned of the performance overhead for evaluating these various instanceof and casting. Is that transform the best I can do in Java or is there a more elegant and/or efficient way of achieving the same behavior?

Below is a trivial, working example printing out BA. I am looking for examples on how to improve that code. In my real code, I have naturally more implementations of 'transform' and none are trivial like below.

public class A implements X {
}

public class B implements X {
}

interface X {
}

public A transform(A a1, A a2) {
  System.out.print("A");
  return a2;
}

public A transform(A a1, B b) {
  System.out.print("B");
  return a1;
}

// Isn't there something better than the code below???
public X transform(X x1, X x2) {
  if ((x1 instanceof A) && (x2 instanceof A)) {
    return transform((A) x1, (A) x2);
  } else if ((x1 instanceof A) && (x2 instanceof B)) {
    return transform((A) x1, (B) x2);
  } else {
    throw new RuntimeException("Transform not implemented for "
            + x1.getClass() + "," + x2.getClass());
  }
}

@Test
public void trivial() {
  X x1 = new A();
  X x2 = new B();
  X result = transform(x1, x2);
  transform(x1, result);
}

Solution

  • Take a look at the Visitor pattern as a starting point.

    If your hierarchy changes a lot, the visitor pattern spreads the changes throughout. In that case, also look at the acyclic visitor.

    The code could look like this:

    public interface X {
      void accept(XVisitor v);
    }
    
    public interface XVisitor { 
      void visit(A a);
      void visit(B b);
    }
    
    public class A implements X {
      public void accept(XVisitor v) {
        v.visit(this);
      }
    }
    
    public class B implements X {
      public void accept(XVisitor v) {
        v.visit(this);
      }
    }
    

    And then your algorithm goes into this class:

    public class XTransformerVisitor implements XVisitor {
      private X result;
      private A first;
      public void visit(A a) {
        if (first == null) first = a;
        else result = a;
      }
      public void visit(B b) {
        if (first == null) throw new RuntimeException();
        result = first;
      }
      public X transform(X x1, X x2) {
        x1.accept(this);
        x2.accept(this);
        return result;
      }
    }