Friday, February 6, 2009

Why not inject dependencies "manually"?

I just read the first half of the Guice User's Guide.  I stopped there because the motivation already left me hanging, and the very first feature I saw seems at odds with dependency injection.

I agree with the general sentiment of the user's guide: don't use new so much, and don't even use static factories.  Instead, "inject"  a class's dependencies via constructor parameters.  That way, the class is abstracted over both the service implementation as well as from where the service comes from.  This style of programming is already emphasized in at least the Joe-E and Scala communities.  I like it.  OO designers like it.  PL developers like it.

However, I don't understand the difficulty in doing this "manually".  The guide gives this lovely example of substituting a mock service in a test case:

public void testClient() {
  MockService mock = new MockService();
  Client client = new Client(mock);

So far so good.  Here we see the payoff of moving the new ServiceImpl() out of Client is that the constructor of a Client can instantiate the service in an unusual way.  Where I get lost is in the instantiation of the normal production version of the client.  The manual gives this code sample:

public static class ClientFactory {
  private ClientFactory() {}
  public static Client getInstance() {
    Service service = ServiceFactory.getInstance();
    return new Client(service);

Where did these two factories come from?  I find them odd, because one of the beauties to me of dependency injection is that it composes well.  In most cases, the production version would know exactly which concrete client and service to instantiate.  In the remaining cases, the code assembling them should itself have parameters influencing how to construct things.  Percolating this idea to the top level of the application, the parameters to the top-level application factory would be precisely those things configurable via configuration files and command-line arguments.

So far my experience matches this intuition.  The Scala compiler is implemented in a dependency-injection style.  Almost all modules find out about their dependencies by having a reference to the dependency passed in at construction time.  The Scala group has not taken advantage of this with testing mocks, even though that would seem straightforward.  However, I can attest that it was straightforward to reassemble the components to make an X10 variant of the compiler.

Overall, I believe that dependency injection is a good style.  However, I have not yet seen an example where it leads to extraneous code.  At least, the example in the guide isn't very good.  Further, the one tool feature I managed to read before being turned away by motivation was the ability to designate a "default" implementation of an interface.  Isn't this a temptation, though, not to use a dependency-injection style?  I would think if you are injecting dependencies, then any code constructing an implementation would already know enough to choose which one to construct.

Probably there is a breakdown somewhere in this argument.  Tools get popular for a reason.  At the least, though, the counter-argument is not documented in any easily findable place.


Robert "kebernet" Cooper said...

While I haven't looked at the Guice docs in a while, I suspect you missed the point of the factory examples... They aren't demonstrating the "Guice" way, they are showing the "Traditional Java Way" people would have done service providers.

The point of Guice or any dependency injector is to keep you from having to write 200 factories in your application. Not to make them easier to work with :)

Rafael de F. Ferreira said...

One advantage of dependency injection frameworks is that they resolve recursive dependencies, so you won't have to.

Lex Spoon said...

Robert, I agree, but the "Traditional Java Way" that is shown is a straw man. Just above that example there is discussion on how to do better. If you compare against crummy code, then anything looks like an improvement.

Rafael, methods and object encapsulation also solve recursive dependencies. They are powerful tools of abstraction, but you have to think to use them.

Blair Zajac said...

There's a good video from Google IO where they go over Guice.

They also show that the factory code is what you shouldn't have in your code.

And thanks for all the good work on Scala. Maybe we can get a GWT Scala compiler???? :)