Our application has two implementations for one interface. EnergySource
is implemented by both Plutonium
and LightningBolt
:
class DeLorean {
@Inject TimeCircuits timeCircuits;
@Inject FluxCapacitor fluxCapacitor;
@Inject EnergySource energySource;
}
interface FluxCapacitor {
boolean isFluxing();
}
@Singleton
class RealFluxCapacitor implements FluxCapacitor {
@Inject TimeCircuits timeCircuits;
boolean isFluxing;
public boolean isFluxing() {
return isFluxing;
}
}
class TimeCircuits {
Date whereYouveBeen;
Date whereYouAre;
Date whereYourGoing;
}
interface EnergySource {
void generateOnePointTwentyOneGigawatts();
}
class Plutonium implements EnergySource { ... }
class LightningBolt implements EnergySource { ... }`</pre>
And to allow for sequels, we assume other implementations of `EnergySource` are possible. We'd like to create an `Injector` immediately and create a Plutonium-powered DeLorean. Shortly thereafter, we'd like to re-use that same `Injector`, but with a lightning bolt for energy.
> ![](/uploaded_images/plutonium.png)
### Option one: Factory classes
We can solve this problem by introducing a `DeLorean.Factory` interface that accepts an `EnergySource` as its only parameter:
<pre class="prettyprint">`class DeLorean {
private final TimeCircuits timeCircuits;
private final FluxCapacitor fluxCapacitor;
private final EnergySource energySource;
DeLorean(TimeCircuits timeCircuits,
FluxCapacitor fluxCapacitor,
EnergySource energySource) {
this.timeCircuits = timeCircuits;
this.fluxCapacitor = fluxCapacitor;
this.energySource = energySource;
}
static class Factory {
@Inject TimeCircuits timeCircuits;
@Inject FluxCapacitor fluxCapacitor;
DeLorean create(EnergySource energySource) {
return new DeLorean(timeCircuits, fluxCapacitor, energySource);
}
}
}`</pre>
This works for our _specific_ problem, but in general it's quite awkward:
-
It requires a gross amount of boilerplate code.
-
It discourages refactoring of the
DeLorean
class.* It increases the complexity of getting anEnergySource
. -
It doesn't work unless
EnergySource
is a direct dependency of theDeLorean
class. Otherwise you need to create lots of little factories that cascade.* AndEnergySource
is no longer in-the-club—it doesn't participate in Guice's injection, AOP, scoping, etc.Option two: AssistedInject
AssistedInject is a Guice extension that's intended to reduce the boilerplate of option one. Instead of a factory class, we write a factory interface plus annotations:
`class DeLorean { TimeCircuits timeCircuits; FluxCapacitor fluxCapacitor; EnergySource energySource;
@AssistedInject
DeLorean(TimeCircuits timeCircuits,
FluxCapacitor fluxCapacitor,
@Assisted EnergySource energySource) {
this.timeCircuits = timeCircuits;
this.fluxCapacitor = fluxCapacitor;
this.energySource = energySource;
}interface Factory {
DeLorean create(EnergySource energySource);
}
}</pre> This fixes some problems. But the core issue still remains: getting an instance of
EnergySourceis difficult. Unlike regular Guice (
@Injectis the new
new), you need to change all callers if you add a dependency on
EnergySource`.Option three: Hierarchical Injectors
The premise is simple.
@Inject
anything, even stuff you don't know at injector-creation time. So ourDeLorean
class would look exactly as it would ifEnergySource
was constant:`class DeLorean { TimeCircuits timeCircuits; FluxCapacitor fluxCapacitor; EnergySource energySource;
@Inject
DeLorean(TimeCircuits timeCircuits,
FluxCapacitor fluxCapacitor,
EnergySource energySource) {
this.timeCircuits = timeCircuits;
this.fluxCapacitor = fluxCapacitor;
this.energySource = energySource;
}
}</pre> To use it, we start with an
Injectorthat had bindings for everything _except_ for
EnergySource. Next, we create a second injector that extends the first, and binds either
Plutoniumor
LightningBolt`. This second injector fills in its missing binding.The injectors share singletons, so we don't have to worry about having multiple
TimeCircuits
. Static analysis is applied to both injectors as a whole, where complete information is known. And all objects are in-the-club and get Guice value-adds like injection, scoping and AOP.This is the solution to the mystical Robot Legs problem, wherein we have a
RobotLeg
class, that needs be injected with either aLeftFoot
or aRightFoot
, depending on where the leg will ultimately be used.Criticism of Hierarchical Injectors
They suggest competing bindings. One parent injector could have relations with multiple child injectors. In our example, the parent injector binds
DeLorean
andTimeCircuits
, and each child binds a differentEnergySource
.They require abstract Injectors. The parent injector in our example wouldn't be able to create an instance of
DeLorean
, since it doesn't have all of the prerequisite bindings. This is just weird.They're complex. Guice was born out of making code simpler. Does the conceptual weight of hierarchical injectors justify their inclusion?
Going forward
Today's Guice includes a simplified implementation of hierarchical injectors written by Dan Halem. It doesn't cover the interesting (but complex) case where the parent injector cannot fulfill all of its bindings. I'm studying the use cases, trying to come up with a balance between ease-of-use and power.
For example, one idea is to require users to explicitly call-out bindings that child injectors will provide:
` public void configure() { bind(EnergySource.class).throughChildInjector(); }
I'd also like to do something similar to AssistedInject's factory interfaces. This way the second injector would be created, used and discarded transparently, so the user never needs to see it. From the user's perspective, this would just be like AssistedInject, but the assisted parameters could be injected anywhere.
If you have suggested use-cases or ideas, I'd love to hear 'em.