Question

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);
}
Was it helpful?

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

OTHER TIPS

The term that you're looking for is multiple dispatch, which is a generalization of virtual functions that are polymorphic in the types of multiple arguments. Most programming languages, including Java and C++, do not have support for multiple dispatch, and so require some sort of hackery to emulate it. One option is to have code like what you have above, and another would be to use something like this. One common solution is to use an idiom called the visitor pattern, which can help abstract away the complexity.

After some more research, I came across the concept of reflection. I think this is by far simpler than the visitor pattern, at least to solve the specific problem here.

My original code above can stay exactly the same and the annoying method transform(X x1, X x2) simply becomes:

public X transform(X x1, X x2) {
  Method m;
  try {
    m = getClass().getMethod("transform",
            new Class[]{x1.getClass(), x2.getClass()});
    return (X) m.invoke(this, new Object[]{x1, x2});
  } catch (Exception e) {
    throw new RuntimeException("Transform not implemented for "
        + x1.getClass() + "," + x2.getClass());
  }
}

Advantages:
- gets rid of the nested instanceof tests and casting I had in my original post
- no need for implementing an accept method that all operands must implement brought by the double-dispatch/visitor pattern approach

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top