Wednesday, June 10, 2009

Dependency Injection: Why Should You Care?

What we want in software development is maximum flexibility accomplished with minimal impact. One mechanism that facilitates this goal is dependency injection (DI), and I've come across a well-written rationale for DI - in Sang Shin's Java Passion coursework on the topic. I appreciate an analysis that takes me from square one in a step-wise fashion, so I'd like to consolidate Mr. Shin's 57-page PDF into a few bullet points here:
  1. Start with a hardwired "hello world" message in a program: not so flexible; you must recompile to change the message.
  2. Use a command line argument to allow the client of your program to specify the message: a bit more flexibility, but the program is coupled to the source of the message (command line input); you need a recompile to alter this. As well, the output format/destination/etc are still hardwired in the program. So, we see two separate concerns: a messenger and a renderer.
  3. Support separation of concerns by providing separate messenger and renderer classes: here, the program supplies a particular instance of messenger to a particular instance of renderer - so these combinations are hardwired. If you have N different messenger types with M different renderer types, you'll have an N * M matrix of API signatures - i.e. the renderer might have a setMessenger(MessengerImpl) call - and the program would have to choose at compile-time from among the N * M possibilities. This is not flexible at all, and certainly not maintainable.
  4. Use interfaces instead of implementations: the program specifies which messenger is supplied to which renderer, but the renderer setter uses a messenger interface instead of a hardwired implementation. Now logic in each can change without affecting the other, but the program is still hardwired to which combination of messenger and renderer is used.
  5. Use a factory to decouple the program from the combination of messenger and renderer: but this requires writing the factory, and the factory itself is still coupled to the combination choice.
  6. Use an external configuration file to decouple the factory from the choices: but, the factory still needs to be written, and the program is still explicitly aware of the source of the information (the factory).
  7. Use an off-the-shelf generic factory: Spring's DefaultListableBeanFactory is a better abstraction, more robust, and you're not reinventing a wheel, but the program is still explicitly aware of the need to wire the pieces together (i.e. it must supply a messenger to a renderer), and you still need to write a facade factory using Spring's generic factory.
  8. Use dependency injection, e.g. Spring: now there's no need to manually supply the dependency; the wiring is done by Spring. The program, messenger and renderer are all unaware of Spring, and these classes are testable without the need for Spring.
  9. Finally, use Spring DI with a constructor argument - now you can configure the message externally. Somewhat analogous to #2, in the sense of externalizing the message content, but in this case the messenger does not know where the message comes from.

No comments:

Post a Comment