views:

101

answers:

3

Hi all,

I recently fumbled into a problem with an API and an implementation where the following type of code appeared:

The API is an abstract class:

public abstract class A {
 public A sum(A a) {
  System.out.println("A.sum(A) called");
  return null;
 }
}

The implementation is a simple class:

public class B extends A {
 public B sum(B b) {
  System.out.println("B.sum(B) called");
  return null;
 }
}

When it comes to using it I write:

public class Main {

  public static void main(String args[]) {
    B b = new B();
    A basa = new B();

    b.sum(b);
    basa.sum(b);
    basa.sum(basa);
  }  
}

Which results in:

% java Main
B.sum(B) called
A.sum(A) called
A.sum(A) called

I understand that B's sum does not override A's sum as its signature is different, but I'd like to provide an efficient implementation of sum for objects of effective type B. I think such design is quite classical and would like to now how I should design my API and implementation so that it is efficient.

Of course, I could provide sum(A a) in class be and check if b is an instanceOf B before calling either sum(B) or super, but I thought that instanceOf was to be avoided for efficiency reasons (if it is inefficient, it may be even less efficient as my "abstract" implementation.

+2  A: 

Since B instances can be summed with A instances using myA.sum(myB), you should be able to change B's definition of sum so that it does override, unless of course sum is a placeholder and isn't something that should be commutative.

UPDATE:

If this is insufficient, you could start getting fancy with generics. Here's a rough pass at what I mean:

public abstract class A {
    public <T extends A> T sum(T a) {
        System.out.println("A.sum(A) called");
        return null;
    }

    public static void main(String args[]) {
        B b = new B();
        b.sum(b);

        A basa = new B();
        basa.sum(b);
        basa.sum(basa);
    }

    public static class B extends A {
        @Override
        public <T extends A> T sum(T b) {
            System.out.println("B.sum(B) called");
            return null;
        }
    }
}

@aioobe is right that the generally accepted work-around is to use the Visitor pattern. I'm offering these as less complete but less verbose alternatives.

Hank Gay
Well, I could add in class B the following method to force the dynamicity: public A sum(A a) { if (a instanceof B) { return this.sum((B) a); } else { return super.sum(a); } }but somehow it will fail my purpose (provide a more efficient implementation as instanceof is usually known as very inefficient).My question was rather on the design pattern, as it seemed to me that this purpose was quite usual.
dodecaplex
@dodecaplex My point was that `sum` is generally understood to be commutative. If you are in fact implementing a sum, a signature of `@Override B sum(A a)` should be enough to get you what you want while still being commutative. I realized that `sum` might just be a placeholder, hence the caveat at the end.
Hank Gay
I don't get how this works. I removed `abstract` from `A`, and tried to invoke `b.sum(a)`, but it still goes to `B`'s implementation of sum.
aioobe
@aioobe If you want `A.sum(A)` to get called, you'd need to actually instantiate an `A`, e.g., `A basa = new A();`. Alternatively, you could create a new subclass of `A` that doesn't override `A`.
Hank Gay
+3  A: 

instanceof can usually be avoided by using the visitor pattern. Depending on your needs, it may or may not be an overkill. It's flexible but quite verbose. In the example below I removed abstract from A to illustrate how it works with different types.

The trick is that when an object is asked to visit a visitor, the object itself chooses the correct accept method in the visitor. The "instanceof"-check is resolved through polymorphism. (I doubt that it's more efficient than an instanceof though.)

interface Visitor {
    public A accept(A a);
    public B accept(B b);
}

class A {
    public A sum(A a) {
        System.out.println("A.sum(A) called");
        return null;
    }

    public A visit(Visitor sv) {
        return sv.accept(this);
    }
}

class B extends A {
    public B sum(B b) {
        System.out.println("B.sum(B) called");
        return null;
    }

    public B visit(Visitor sv) {
        return sv.accept(this);
    }
}

public class Test {

    public static void main(String[] args) {
        A a = new A();
        B b = new B();
        A basa = new B();

        a.visit(new SumVisitor(b));        // a.sum(b);
        b.visit(new SumVisitor(b));        // b.sum(b);
        basa.visit(new SumVisitor(b));     // basa.sum(b);
        basa.visit(new SumVisitor(basa));  // basa.sum(basa);
    }

    static class SumVisitor implements Visitor {
        A arg;
        SumVisitor(A arg) { this.arg = arg; }
        public A accept(A a) { return a.sum(arg); }
        public B accept(B b) { return b.sum(arg); }
    }
}

Output:

A.sum(A) called
B.sum(B) called
B.sum(B) called
B.sum(B) called

Disclamer; It was a while ago I wrote a visitor, so please correct me if I have any bugs in this (almost untested) code snippet. Or better, edit the post yourself and improve it :)

aioobe
OK, it seems overkill to me and I'd like to test it's efficiency (to evaluate the difference between instanceof and polymorphism). I'll post here if I have some figures.
dodecaplex
There is a bug in `SumVisitor`, but I'm not clever enough to fix it - you never use `arg` and just add everything to itself, meaning that this `x.visit(new SumVisitor(y))` just a roundabout way of writing `x.sum(x)`. Just changing `accept(B)` to `return b.sum(arg)` or `return arg.sum(b)` won't work as both resolve to `sum(A)` then.
gustafc
@gustafc, Yep. You're right. I had a bad feeling about it when I posted it... thus the disclamer ;) So I suppose some double dispatch is needed. One way to fix it would be to divide the SumVisitor into a LHS visitor and a RHS visitor.
aioobe
A: 

So, what makes you think instanceof is slow? It's used in several places in the JDK where they want to provide a "fast path" for certain well-known implementations of an abstract class or interface. The usual advice applies here: "Test, don't guess."

gustafc
You are right. It's just what I read from discussion on several mailing list. I think some people use it as an argument to justify certain design in open-source XML parser.If I recall correctly the stax api uses an enumerate to distinguish between a node/Comment/Attribute event instead of relying on instanceof.If I have time to write a test, I'll post the results here...
dodecaplex