Take argument that implements several Interfaces for object creation
I have object that I want to create using object that implements two interfaces (sup开发者_运维技巧pose, I can't modify object's class to create third interface that will be extends two interfaces). What will be the best way to create such objects using guice?
Java does allow you to do interface combinations, i.e.
static class TL<T extends IA & IB> extends TypeLiteral<T>(){}
but you'll notice there really has to be a concrete type T which implements both of these interfaces. Java can't invent "combination types" for variables - a type must exist with an actual class file.
I was surprised to discover that it is indeed possible to do this. I suspect this is a dark corner that Guice's maintainers would suggest not going into, and it is probably not a good idea.
Things to notice about the code below:
- I am actually abusing Guice's built-in safeguards by making TL subclass TypeLiteral parameterized on IA instead of TypeLiteral on T which would be correct. So in fact, this entire example probably works by accident.
- It doesn't help that much. Something, somewhere finally has to specify a concrete type (the class Both) in this case. It lets you get around specifying a concrete type in the classes which use the object, but not when you bind it or if you request it directly from the injector
- Almost nobody understands the TypeA & TypeB syntax with Java generics - prepare for anybody who looks at this code to be completely baffled, even some Java gurus
- If you later try to use an object which only implements one of the interfaces, you could create a dynamic proxy which implements one interface and delegates to the object, but you will still need to create an interface which combines the two to give Java a type to refer to
public class X {
static final class TL<T extends IA & IB> extends TypeLiteral<IA> {}
interface IA {}
interface IB {}
static final class Both implements IA, IB {}
@Test
public void test() {
Injector inj = Guice.createInjector(new M());
Both object = inj.getInstance(Key.get(new TypeLiteral<Both>(){}));
assertNotNull(object);
Foo<Both> foo = inj.getInstance(Key.get(new TypeLiteral<Foo<Both>>() {}));
assertTrue (object instanceof IA);
assertTrue (object instanceof IB);
assertNotNull(foo);
assertNotNull(foo.obj);
}
static class Foo<T extends IA & IB> {
private final T obj;
@Inject
Foo(T obj) {
this.obj = obj;
}
}
static class M extends AbstractModule {
@Override
protected void configure() {
bind(new TL<Both>()).to(Both.class);
}
}
}
So I think the answer is, you can but you probably shouldn't.
Either inject one interface and cast to the other when needed or inject the same object twice as two different interfaces.
Both are ugly, I know, but I'd call the whole approach ugly. If you need two different interfaces a single class implements you probably haw a flaw in your object model. If one class implements two disjoint interfaces it has two different aspects and you should probably improve it's cohesion
Why not just do:
// Your original class
class AB implements IA, IB {...}
// In a Module
bind(AB.class).in(SOMESCOPE);
bind(IA.class).to(AB.class);
bind(IB.class).to(AB.class);
// In the object to be inejcted with AB
class MyClass {
@Inject IA a;
@Inject IB b;
}
In the end, you would actually have a == b
, wouldn't that fit the bill?
精彩评论