Brian Slesinsky's Weblog

Wednesday, 23 Jan 2008

Inject is the new import

import com.example.Thing;
import com.example.Thing;
private Thing thing;

I've written before about how Guice works, but didn't explain why you would want to use it. Here's one way to think about it.

I think of an import as a way of wishing for a class: "I wish I had a TurkeySandwich." Poof! It magically appears. "Thanks, classloader!" You only need to know a class's true name to summon it, at least as far as source code is concerned. As a result, adding a new dependency from one class to another is trivial in Java. (Maybe too trivial, given some of the spaghetti code out there.)

Now, instead of wishing for a class, let's say we want an instance of a class. If you're deep down in the guts of large system, doing this properly can be significantly more work. We can add a new field, but then we have to initialize it in a constructor or setter, and the data that goes into the field could be far away. Sometimes this means modifying hundreds of constructor calls. A refactoring IDE makes it easier, but it's still a much bigger change than adding an import statement.

Naturally we want to find an easier solution. Constants, static method calls, and thread locals have their place, but such shortcuts can really mess up the code base. Test-first programming can help keep us on the straight and narrow, but it doesn't reduce the pain of refactoring to get data for a new dependency to the right place.

Suppose that instead we made instances easy to wish for? That's where Guice comes in. Stripped to its essentials, Guice lets you wish for an instance of a class by putting an @Inject annotation on a field. Your class simply assumes that the environment will initialize the instance correctly; exactly how this happens is none of your concern, just like you don't need to know anything about classpaths or classloaders to write an import statement.

(Guice also has constructor and method injection, but I think of them as a way to fit Guice into legacy systems. They're not necessary unless there is some code somewhere that doesn't use Guice to create instances. I use Guice injection even when writing tests.)

Of course, it's not really that simple. For the genie to be able to grant our wishes, we have to set up the environment. For import statements, that means setting up a classpath, or writing class loaders in the more complex cases. For Guice, we set up the environment by writing annotations for simple cases and writing Guice modules for more complex ones. Luckily, writing a Guice module is much easier than writing a class loader.

Assuming the environment is set up, how do you structure your code differently when using Guice?

I find that a lot of initialization clutter goes away. Classes don't need five different constructors that take slightly different arguments; in fact, they sometimes don't need any constructors at all. We can also get rid of static methods that essentially act as constructors, and don't need to write getInstance() methods for singletons.

Also, when you get rid of "convenience" constructors, you may find that each class has fewer imports, and those are only to the class's nearest neighbors in the dependency graph, making the that much more modular and easier to change.

However, in their place, you can expect to see more factories. Guice only allows you to wish for instances by class name and annotations; you can't pass any runtime parameters. Instead, you can wish for a factory and call a method on the factory. (This is admittedly a different kind of boilerplate that I hope will go away in a future version of Guice. For some workarounds for simple cases, see Guice with Curry and AssistedInject.)

A harder case is what we usually call the "robot legs" problem. Say I'm the AnkleBone class, and I know that, according to the song, I need to be connected to the KneeBone, so I wish for a KneeBone and Guice can't do it because it doesn't know which KneeBone to return. The AnkleBone may be generic, so it doesn't know either. Guice is missing a way to create multiple, custom instances of large graphs of objects and join them together.

Until this is fixed, we'll have to use factories that call other factories and pass parameters the old-fashioned way. I have a few ideas on how to extend Guice, but that will have to wait for another time. For now, Guice 1.0 goes a long way towards cleaner code.