Monday, March 14, 2011

Using Guice for dependency injection

So last time I described what dependency injection is. In this post I'll run quickly through how you can use a dependency injection framework. I'm going to pick Guice since I'm familiar with it, and because it's really straightforward.

You instruct Guice on how to do injection using a "module".
public class FlamingoModule extends AbstractModule {
  @Override protected void configure() {
    bind (RouletteBall.class);
    bind (RouletteTable.class);
    bind (Integer.TYPE).annotatedWith (SeatsPerTable.class)
      .toInstance (8);
    bind (SpecialtyDrink.class).to (Margarita.class);
  }
  @Provides RouletteWheel provideRouletteWheel (RouletteBall ball) {
    return new RouletteWheel (ball, true);
  }
}
This module's configure() method sets up some bindings, which is how Guice gets from what you ask it for to what it gives you. The first two bindings just make Guice aware of the RouletteBall and RouletteTable classes. The last one tells Guice that whenever someone asks for a SpecialtyDrink from Guice, it should deliver an instance of the Margarita class.

The third binding tells Guice that whenever it's asked for an integer annotated with @SeatsPerTable, it should send back the value 8. Annotations are a way that you can have Guice inject different values for the same type. Generally you need to code up the annotations yourself.

The provideRouletteWheel() method illustrates a different way Guice can get you objects. When Guice is asked for a RouletteWheel instance, it will run the "provider" method to create one. Guice handles injecting a RouletteBall instance into the method's ball parameter.

In order to finish wiring up the code, we'll need to use the @Inject annotation. This tells Guice to inject a dependency. So, the RouletteTable constructor needs a little work.
@Inject public RouletteTable (RouletteWheel w,
                              @SeatsPerTable int n) {
  numberOfSeats = n;
  wheel = w;
}
Now, when Guice needs a RouletteTable, it knows about it (from the module) and knows to inject values into its constructor. The third binding in the module's configure() method lets it inject n, and the provider method takes care of w. Since Guice also knows about RouletteBall, it can inject an instance of that class into the provider method.

To finally tie it all together, you need a starting point, something that lets you talk to Guice. That's called an injector.
Injector injector = Guice.createInjector (new FlamingoModule());
RouletteTable t = injector.getInstance (RouletteTable.class);
You can see that I could define a new module for a different casino, say, one that uses European roulette wheels and seats ten per table, and use that module with Guice to get a differently constructed RouletteTable object. The knowledge of how to create objects is wrapped up nicely in the modules and doesn't interfere with the use of the objects.

So, after all this, you may wonder why this is a good idea. What does this buy you, besides the kind of abstract architectural benefits?

One thing you get is not having to call a bevy of constructors just to make a high-level object. Instead of new this and new that getting passed to new something else, the "rules" for creating the objects are laid out in a more declarative fashion.

Another thing is get is tighter control of object creation. For example, Guice lets you inject dependencies as singletons, so you only ever get one instance across all injections. As another example, a provider method gives you free rein to control exactly how objects are built.

Perhaps the most powerful thing you get is simple swapping of object creation systems. Suppose you want to perform some testing of the code that uses RouletteTable, and you need to be able to peek into and tweak the RouletteTable and RouletteWheel instances. No problem:
Injector injector = Guice.createInjector (new TestingModule());
Now you can create a module that generates objects designed for testing purposes. The code that uses those objects doesn't need to change at all.

That's quite enough about Guice. For more, check out its user's guide. Hopefully this quick tour of Guice has shown you how neat dependency injection is and what it can do for you.

No comments:

Post a Comment