What do I do with Guice when I need to call a parent constructor that is also injectable? e.g. I have an abstract parent class that has a constructor that is injected with a
You'd need to do the exact same thing you do if you weren't using Guice... declare any parameters the parent constructor requires as parameters to each child's constructor as well, and pass those to super
.
So if your abstract parent class's constructor takes a Foo
, a child class's constructor needs to look like:
@Inject public ChildClass(Foo foo, Bar bar) {
super(foo);
this.bar = bar;
...
}
A better alternative is to use something similar to the strategy pattern to encapsulate all the fields the superclass wants to inject, and then the subclass can inject that. For example:
public abstract class Animal {
/**
* All injectable fields of the Animal class, collected together
* for convenience.
*/
protected static final class AnimalFields {
@Inject private Foo foo;
@Inject private Bar bar;
}
private final AnimalFields fields;
/** Protected constructor, invoked by subclasses. */
protected Animal(AnimalFields fields) {
this.fields = fields;
}
public Foo getFoo() {
// Within Animal, we just use fields of the AnimalFields class directly
// rather than having those fields as local fields of Animal.
return fields.foo;
}
public Bar getBar() {
return fields.bar;
}
}
public final class Cat extends Animal {
private final Whiskers whiskers;
// Cat's constructor needs to inject AnimalFields to pass to its superclass,
// but it can also inject whatever additional things it needs.
@Inject
Cat(AnimalFields fields, Whiskers whiskers) {
super(fields);
this.whiskers = whiskers;
}
...
}
Buried in the Minimize Mutability section of the Guice Best Practices, you'll find this guideline:
Subclasses must call
super()
with all dependencies. This makes constructor injection cumbersome, especially as the injected base class changes.
In practice, here's how to do it using constructor injection:
public class TestInheritanceBinding {
static class Book {
final String title;
@Inject Book(@Named("GeneralTitle") String title) {
this.title = title;
}
}
static class ChildrensBook extends Book {
@Inject ChildrensBook(@Named("ChildrensTitle") String title) {
super(title);
}
}
static class ScienceBook extends Book {
@Inject ScienceBook(@Named("ScienceTitle") String title) {
super(title);
}
}
@Test
public void bindingWorked() {
Injector injector = Guice.createInjector(new AbstractModule() {
@Override protected void configure() {
bind(String.class).
annotatedWith(Names.named("GeneralTitle")).
toInstance("To Kill a Mockingbird");
bind(String.class).
annotatedWith(Names.named("ChildrensTitle")).
toInstance("Alice in Wonderland");
bind(String.class).
annotatedWith(Names.named("ScienceTitle")).
toInstance("On the Origin of Species");
}
});
Book generalBook = injector.getInstance(Book.class);
assertEquals("To Kill a Mockingbird", generalBook.title);
ChildrensBook childrensBook = injector.getInstance(ChildrensBook.class);
assertEquals("Alice in Wonderland", childrensBook.title);
ScienceBook scienceBook = injector.getInstance(ScienceBook.class);
assertEquals("On the Origin of Species", scienceBook.title);
}
}