Guicin’ Up Abstract Factories Like a DI Boss
--
The MapBinder in Guice provides a convenient way to do modular component registration.
In your module’sconfigure()
logic, you can bind some objects to some class definitions, and you now have a module dictionary.
A basic example is:
public class FruitModule extends AbstractModule {
...
@Override
protected void configure() {
fruitBindings = MapBinder.newMapBinder(
binder(),
String.class,
Fruit.class
);
fruitBindings.addBinding("Apple").to(Apple.class);
}
...
}
Elsewhere, you can inject the mapping and use it to get Apple
instances by name:
public class FruitBasket { private final List<Fruit> basket; private final Map<String, Fruit> nameToFruitMap; @Inject
public FruitBasket(final Map<String, Fruit> nameToFruitMap) {
this.basket = new ArrayList<>();
this.nameToFruitMap = nameToFruitMap;
} public void addToBasket(final String fruitName) {
basket.add(nameToFruitMap.get(fruitName));
}
}
This works great, except if you have a ton of objects in the mapping, or your FruitModule
is already long and complicated.
Maintaining a Registry
It would be cool, then, if you could just instantiate a regular map, and iterate over it to add bindings instead of writing:
fruitBindings.addBinding("Apple").to(Apple.class);
For each key-value pair.
Similarly, if your module is using multiple MapBinder
s, this problem will already be exacerbated.
What we can do is to maintain a registry, in a new FruitRegistry
class.
private static final Map<Intent, Class<? extends Fruit>> REGISTERED_FRUITS =
ImmutableMap.of(
"Apple", Apple.class,
"Orange", Orange.class
);
And then provide a method in the registry which adds its registered components to a MapBinder
:
public static void bindRegistrations(
final MapBinder<String, Fruit> fruitBindings) {
REGISTERED_FRUITS.forEach((key, value) ->
fruitBindings.addBinding(key).to(value)
);
}
Now, back in your Guice module, all you need to do inflate the registry is to invoke that method on the MapBinder
you are creating:
FruitRegistry.bindRegistrations(MapBinder.newMapBinder(
binder(), String.class, Fruit.class
));
Using it as an Abstract Factory
The above is kind of contrived since it only deals with a simple POJO.
However, the approach above becomes increasingly useful as you deal with more complicated and interactive objects. In particular, it provides a great way to do a clean Abstract Factory.
The standard Abstract Factory lands you eventually with some code like:
FruitFactory factory = null;if (fruitInput.equals("apple")) {
factory = AppleFactory();
} else if (fruitInput.equals("orange")) {
factory = OrangeFactory();
} else if (fruitInput.equals("banana")) {
factory = BananaFactory();
}Fruit fruit = factory.createFruit();
Whenever you see big blocks of code like that, a siren should go off in your head. This is nasty looking stuff.
With the module registry described above, our logic would be added as a key-value pair to the registration map. From there, the selection of a concrete factory is just:
FruitFactory factory = nameToFruitFactoryMap.get(fruitInput);
Fruit fruit = factory.createFruit();
Much cleaner.
Final Thoughts
Above, I still maintain a static registry. I don’t think there’s anything that forces you do this, however. You could dynamically generate the map for the registry class at run-time. Then, you could inject the module lazily, resulting in late binding of the items in the registry.
Have fun!