Prefer dependency injection to hardwiring resources
A static utility class looked tidy, until I tried to test it. Notes on Effective Java's Item 5, and why it stuck with me.
A problem
I was writing a test for a class that checked words against a dictionary. Simple enough. Except the class looked like this:
// Static utility: looks tidy, but inflexible and untestable!
public class SpellChecker {
private static final Lexicon dictionary = new EnglishDictionary();
private SpellChecker() {} // noninstantiable
public static boolean isValid(String word) {
return dictionary.containsWord(word);
}
public static List<String> suggestions(String typo) {
return dictionary.suggest(typo);
}
}
The dictionary is created inside the class, once, forever. My test wanted a tiny fake dictionary with five words. There was no way to give it one. The test had to load the real English dictionary, and I couldn’t test the Vietnamese one at all without changing the class.
Around the same time I was reading Effective Java, and Item 5 described my exact problem: “Prefer dependency injection to hardwiring resources.”
What’s actually wrong
The class looks clean. No state to pass around, just call SpellChecker.isValid("hello"). The problem is hidden in one line:
private static final Lexicon dictionary = new EnglishDictionary();
The class decides for itself which dictionary it uses. That means:
- You can’t swap it. Another language, a specialised vocabulary, a newer version: all need a code change.
- You can’t test it in isolation. Every test uses the real dictionary.
- It’s hidden. Nothing in the method signatures tells you this class depends on a dictionary at all.
Making it a singleton instead of a static utility doesn’t help. It’s the same hardwiring, just with an instance.
The fix: pass the resource in
Instead of the class creating its dependency, whoever creates the class provides it:
public class SpellChecker {
private final Lexicon dictionary;
public SpellChecker(Lexicon dictionary) {
this.dictionary = Objects.requireNonNull(dictionary);
}
public boolean isValid(String word) {
return dictionary.containsWord(word);
}
public List<String> suggestions(String typo) {
return dictionary.suggest(typo);
}
}
That’s it. That’s dependency injection in its simplest form, no framework required. Now:
// production
var checker = new SpellChecker(new EnglishDictionary());
// another language
var vietnamese = new SpellChecker(new VietnameseDictionary());
// test
var testChecker = new SpellChecker(new FakeLexicon("apple", "banana"));
The dependency is visible in the constructor, it’s final so it can’t change after creation, and many SpellCheckers with different dictionaries can live side by side.
This is also exactly what Spring does for you with constructor injection. Seeing the plain-Java version first made Spring’s @Autowired feel a lot less magical.
A useful variant: inject a factory
Sometimes a class doesn’t need one instance of a resource, it needs to create many. In that case, pass in a factory. Since Java 8, Supplier<T> is a perfect fit:
public class Mosaic {
public static Mosaic create(int rows, int cols, Supplier<? extends Tile> tileFactory) {
Mosaic mosaic = new Mosaic(rows, cols);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
mosaic.addTile(i, j, tileFactory.get()); // a new tile each time
}
}
return mosaic;
}
}
// usage
Supplier<Tile> blueTiles = () -> new Tile("blue");
Mosaic mosaic = Mosaic.create(3, 3, blueTiles);
Mosaic doesn’t know or care what kind of tile it gets. The caller decides. The ? extends Tile lets you pass a factory for any subtype of Tile.
What I took away
The rule from the book, which I now repeat to myself:
Static utility classes and singletons are inappropriate for classes whose behaviour is parameterised by an underlying resource.
Static utilities are still great for things that truly have no dependencies, like Math.max. But the moment a class’s behaviour depends on something (a dictionary, a database, a clock, a config), that something should come in through the front door, not be built in the back room.
The test that started all this ended up being four lines long. That was the best sign the design had improved.
References
- Joshua Bloch, Effective Java, 3rd edition, Item 5
Discussion
Enjoy!