views:

249

answers:

6

I think, the following can't be done in Java. But I would be happy to learn how to implement something that resembles it.

Suppose we have a class C, that is already used in compiled code. (We can neither change that code nor the original definition of C).

Suppose further there is interesting code that could be re-used, if only C would implement interface I. It is, in fact, more or less trivial to derive D that is just C + the implementation of the interface methods.

Yet, it seems there is no way, once I have a C, to say: I want you to be a D, that is, a C implementing I.

(Side remark: I think the cast (D)c, where c's runtime type is C, should be allowed if D is a C and the only difference to C are added methods. This should be safe, should it not?)

How could one work around this calamity?

(I know of the factory design pattern, but this is not a solution, it seems. For, once we manage to create D's in all places where formerly were C's, somebody else finds another interface J useful and derives E extends C implements J. But E and D are incompatible, since they both add a different set of methods to C. So while we can always pass an E where a C is expected, we can't pass an E where a D is expected. Rather, now, we'd need a new class F extends C implements I,J.)

+9  A: 

Couldn't you use a delegate class, i.e. a new class which wraps an instance of "Class C", but also implements "Interface I" ?

public class D implements I {

    private C c;

    public D (C _c) {
        this.c = _c;
    }

    public void method_from_class_C() {
        c.method_from_class_C();
    }
    // repeat ad-nauseum for all of class C's public methods
    ...

    public void method_from_interface_I() {
        // does stuff
    }
    // and do the same for all of interface I's methods too
}

and then, if you need to invoke a function which normally takes a parameter of type I just do this:

result = some_function(new D(c));
Alnitak
why not simply "class D extends C implements I"?
dfa
because it either requires that you construct with 'new D(...)' instead of 'new C(...)', which may not be possible. It's also not possible to do "D d = (D)c" even though D extends C. Downcasts from D to C should be possible, though.
Alnitak
I tried to explain that before.I get C's from code not under my control and want to pass them (not a new/different object) to code that works with the interfaces.
Ingo
that's the point of the delegate - yes, it does create a new (temporary) object, but that's necessary because you can't cast a C into a D.
Alnitak
and the dynamic proxy stuff has just the same properties - you still end up passing around something other than the original object, and on top of that it's far harder to understand...
Alnitak
Might want to check for null in that code. Possibly better, make the constructor private and use a static creation method that returns null for a null c.
Tom Hawtin - tackline
I checked that c wasn't null before passing it to the constructor ;-)
Alnitak
+6  A: 

If all that you need to be compatible with is interfaces then no problem take a look at dynamic proxy classes, its basically how you implement interfaces at runtime in java.

if you need similar runtime compatibility with classes I suggest you take a look at cglib or javaassist opensource libraries.

MahdeTo
This looks quite complex, but I'll definitely give it a try. Can you say something about runtime cost, i.e. it looks like I need an extra invokation handler instance per object.
Ingo
The delegate method would be much simpler, this adds complexity without any benefit for this particular problem.
Robin
my thought was that another solution was needed other than applying the adapter pattern specifically a runtime solution. maybe a misinterpretation
MahdeTo
+3  A: 

If you (can) manage the ClassLoader that loads your class C then you can try to do some class-loading time shenanigans with bytecode instrumentation to make the class implement the interface.

The same can be done during build-time, of course. It might even be easier this way (as you don't need access to the ClassLoader).

Joachim Sauer
+1  A: 

I believe what you want is possible by using java.lang.reflect.Proxy; in fact I have done something similar for a current project. However, it's quite a bit of work, and the resulting "hybrid objects" can expose strange behaviour (because method calls on them are routed to different concrete objects, there are problems when those methods try to call each other).

Michael Borgwardt
+2  A: 

(Side remark: I think the cast (D)c, where c's runtime type is C, should be allowed if D is a C and the only difference to C are added methods. This should be safe, should it not?)

Not at all. If you could make this cast, then you could compile code that attempted to call one of the "added methods" on this object, which would fail at runtime since that method does not exist in C.

I think you are imagining that the cast would detect the methods that are "missing" from C and delegate them to D automatically. I doubt that would be feasible, although I can't speak to the language design implications.

It seems to me the solution to your problem is:

Define class D, which extends C and implements I
Define a constructor D(C c) which essentially clones the state of the given C object into a new D object.
The D object can be passed to your existing code because it is a C, and it can be passed to code that wants an I because it is an I

Dave Costa
I meant (D)c to actually change the runtime type of c to D (by just overwriting the vtable-pointer, for instance). Of course, this should only be possible if the compiler can prove that a runtime cast from C to D does not change the Cish behaviour of the object, but just adds new functionality.
Ingo
It's normally only possible to downcast (i.e. from a subclass to its superclass) because that _removes_ functionality and data members.
Alnitak
A: 

I think you that can't do it because Java is strictly typed. I believe it can be done in languages like Ruby and Python with a usage of mixins.

As for Java it definitely looks like a good usage for the Adapter design pattern (it was already proposed earlier as a "wrapper" object).

Nikolay Ivanov