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:
Foo
in my example and not any EnhancedFoo
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:
static interface FooFactory {
public <T extends Foo> T getFoo();
}
Then thanks to type inference, this will compile:
FooFactory f = new EnhancedFooFactoryImpl();
EnhancedFoo e = f.getFoo();
(this may not work prior to Java 8)
If the FooFactory
is not what you expected, the line EnhancedFoo e = f.getFoo();
will throw a ClassCastException
.
Why not parametrize Factory interface ?
static interface FooFactory<T extends Foo> {
public T getFoo();
}
then:
class EnhancedFooFactoryImpl implements FooFactory<EnhancedFoo> {
@Override
public EnhancedFoo getFoo() { return new EnhancedFooImpl(); }
}
And instantiation:
FooFactory<EnhancedFoo> f1 = new EnhancedFooFactoryImpl();
EnhancedFoo foo = f1.getFoo();
The enhanced factory of course can be used where base class is expected.
FooFactory<?> f2 = new EnhancedFooFactoryImpl();
Foo foo = f2.getFoo();
EDIT (on reply to Your comment)
If for your design it is better to have parametrized factory method not the factory class then it is a good practice to define it as below:
interface FooFactory {
public <T extends Foo> T getFoo(Class<T> fooClass);
}
This gives you advantage in two ways: 1. You can control which actuall clas sa user wants to create. 2. Having a class object you can instantiate it with reflection.
So in this case you do not have to have to special Factory classes for enhanced foo:
class FooFactoryImpl implements FooFactory {
@Override
public <T extends Foo> T getFoo(Class<T> c) {
try {
return c.newInstance();
} catch (ReflectiveOperationException e) {
return null;
}
}
}
Then usage is like below:
FooFactory ff = new FooFactoryImpl();
EnhancedFoo ef = ff.getFoo(EnhancedFoo.class);
Foo f = ff.getFoo(Foo.class);
If some Foo implementation requires constructor parameters you can always put corresponding if in the foctory method and instantiate the object "manually":
@Override
public <T extends Foo> T getFoo(Class<T> c) {
if(SomeParametrizedFoo.class.equals(c)) {
SomeParamtrizedFoo spf = new SomeParamtrizedFoo("constr arg");
spf.setParam(16136);
return (T) spf;
}
try {
return c.newInstance();
} catch (ReflectiveOperationException e) {
return null;
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With