I'm working on an API design, and there's something about Java and polymorphism that I didn't think about until now. If I create an API like this:
interface FooFactory
{
public Foo getFoo();
}
interface Foo
{
public void sayFoo();
}
then the only thing that my FooFactory
implementation can be relied upon to provide is a Foo
implementation. If I decide to provide some enhanced methods, like this:
interface EnhancedFoo extends Foo
{
public void patHeadAndRubBelly();
}
class EnhancedFooImpl implements EnhancedFoo
{
... implementation here ...
}
class EnhancedFooFactoryImpl implements FooFactory
{
@Override public EnhancedFoo getFoo() { return new EnhancedFooImpl(); }
}
and the only way my API clients can use the EnhancedFoo
interface is if they obtain a Foo
interface and try to cast it as the EnhancedFoo
.
I remember the way Microsoft handled COM in the IUnknown
interface:
HRESULT QueryInterface(
[in] REFIID riid,
[out] void **ppvObject
);
The idea is that you pass in a GUID for the interface you want, if it succeeds, you get back a pointer that guarantees you can safely cast it to the interface you were looking for.
I could do something similar in a typesafe way with Java:
interface FooFactory
{
public <T extends Foo> T getFoo(Class<T> fooClass);
}
where I provide an implementation on request that either returns an instance of the desired subinterface, or returns null if none is available.
My question, is:
- is the QueryInterface pattern reasonable?
- should I just use casting?
- or is the only right way to handle polymorphism in the API to restrict clients to strictly using the plain methods in question? (e.g. the methods in
Foo
in my example and not anyEnhancedFoo
methods)
Clarification: The factory implementation isn't going to be known by client code. (Let's say it uses dependency injection or some service provider architecture like java's ServiceLoader
or NetBeans Lookup
.) So as a client, I don't know what's available. The factory might have access to several Foo
derivatives and I want the client to be able to request a feature set that it wants, and either it gets that, or it will have to fall back on the base Foo
functionality.
I guess the hard part for me is that there is runtime dependency here... the pure static typesafe approach where everything is fixed at compile time means that I can only depend on that basic Foo
functionality. That approach is familiar to me, but then I lose out on possible enhanced features. Whereas the more dynamic / opportunistic approach is something that can take advantage of these features, but I'm not sure of the right way to architect a system that uses it.
You could simply declare your factory with generics and leave the rest as is:
Then thanks to type inference, this will compile:
(this may not work prior to Java 8)
If the
FooFactory
is not what you expected, the lineEnhancedFoo e = f.getFoo();
will throw aClassCastException
.