I am working on an API design, and there is something about Java and polymorphism that I have not thought about so far. If I create an API as follows:
interface FooFactory { public Foo getFoo(); } interface Foo { public void sayFoo(); }
then the only thing on which the FooFactory implementation can be implemented for the implementation is the Foo implementation. If I decide to provide some advanced methods, for example:
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 to get the Foo interface and try to use it as EnhancedFoo .
I remember how Microsoft handled COM in the IUnknown interface:
HRESULT QueryInterface( [in] REFIID riid, [out] void **ppvObject );
The idea is that you pass in the GUID for the interface you need, if it succeeds, you get a pointer that ensures that you can safely apply it to the interface you were looking for.
I could do something like this with Java:
interface FooFactory { public <T extends Foo> T getFoo(Class<T> fooClass); }
where I provide an on-demand implementation that returns an instance of the required subinterface or returns null if none of them are available.
My question is:
- Is the QueryInterface pattern reasonable?
- Should I just use casting?
- or is it the only correct way to handle polymorphism in the API to restrict customers to strictly using the simple methods in question? (e.g. methods in
Foo in my example, not any EnhancedFoo methods)
Explanation: The factory implementation will not be known by the client code. (Let's say it uses dependency injection or some service provider architecture, such as java ServiceLoader or NetBeans Lookup .) So, as a client, I don't know what is available. factory can have access to several Foo derivatives, and I want the client to be able to request the feature set that he wants, and either he will get it, or he will have to return to the basic Foo functionality,
I believe the hard part for me is that there is a dependency on execution. A pure static approach, when everything is fixed at compile time, means that I can only depend on the underlying Foo function. This approach is familiar to me, but then I lose the opportunity for improved features. While a more dynamic / opportunistic approach is something that can take advantage of these features, but I'm not sure about the correct way to create a system that uses it.