A standard object factory may look like this:
interface I { ... }
class A implements I { ... }
class B implements I { ... }
class IFactory {
I getI(int i) {
switch (i) {
case 1: return new A();
default: return new B();
}
}
}
Is it possible to set up bindings so that switch is done for me, i.e. all I do is call getInstance or inject? I was looking at assisted injection but that seems to be different topic: https://code.google.com/p/google-guice/wiki/AssistedInject
Using GuiceIn each of your constructors that need to have something injected in them, you just add an @Inject annotation and that tells Guice to do it's thing. Guice figures out how to give you an Emailer based on the type. If it's a simple object, it'll instantiate it and pass it in.
If you're working on an Android application, reflection is very slow. This means that using Guice could have a noticeable effect on performance and Dagger is probably the right answer for you. If you're working on a Java application, then your options are more open.
Guice is an open source, Java-based dependency injection framework. It is quiet lightweight and is actively developed/managed by Google. This tutorial covers most of the topics required for a basic understanding of Google Guice and to get a feel of how it works.
getInstance(...) , Guice doesn't need to do any reflection. It creates the RealBillingService right then, injecting all its (transitive) dependencies, and returns that (not a proxy). If you use Guice's AOP functionality then those objects will be proxies, but otherwise Guice doesn't return proxies.
It sounds like you're looking for a MapBinder
, which is part of the Multibindings feature. Note that you'll still need to put in some kind of IFactory
or other factory interface, because getInstance
doesn't take a parameter the way your getI
does, and you'll still need to establish a mapping from integer to class implementation somewhere.
class IModule extends AbstractModule {
@Override public void configure() {
MapBinder<Integer, I> myBinder =
MapBinder.newMapBinder(binder(), Integer.class, I.class);
myBinder.addBinding(1).to(A.class);
// Add more here.
}
}
// You can even split the MapBinding across Modules, if you'd like.
class SomeOtherModule extends AbstractModule {
@Override public void configure() {
// MapBinder.newMapBinder does not complain about duplicate bindings
// as long as the keys are different.
MapBinder<Integer, I> myBinder =
MapBinder.newMapBinder(binder(), Integer.class, I.class);
myBinder.addBinding(3).to(C.class);
myBinder.addBinding(4).to(D.class);
}
}
An injector configured with those modules will provide an injectable Map<Integer, I>
that has an instance of everything bound; here it would be a three-entry map from 1 to a fully-injected A
instance, from 3 to a C
instance, and from 4 to a D
instance. This is actually an improvement over your switch example, which used the new
keyword and thus didn't inject any dependencies into A
or B
.
For a better option that doesn't create so many wasted instances, inject a Map<Integer, Provider<I>>
that MapBinder also provides automatically. Use it like this:
class YourConsumer {
@Inject Map<Integer, Provider<I>> iMap;
public void yourMethod(int iIndex) {
// get an I implementor
I i = iMap.get(iIndex).get();
// ...
}
}
To provide a "default" implementation (and opaque interface) the way you did, though, you'll want to implement your own short wrapper on top of the MapBinder
map:
class IFactory {
@Inject Map<Integer, Provider<I>> iMap;
@Inject Provider<B> defaultI; // Bound automatically for every Guice key
I getI(int i) {
return iMap.containsKey(i) ? iMap.get(i).get() : defaultI.get();
}
}
If the above looks like overkill, remember that you can inject an Injector
and create a local Map
from key to implementation. (You can also use ImmutableMap
like I did here).
class IFactory {
@Inject Injector injector; // This is a bad idea, except for times like this
@Inject Provider<B> defaultI;
static final ImmutableMap<Integer, Class<? extends I>> map = ImmutableMap.of(
1, A.class,
3, C.class,
4, D.class);
I getI(int i) {
return map.containsKey(i)
? injector.getInstance(map.get(i))
: defaultI.get();
}
}
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