Sunday, September 26, 2010

Dependency Injection Part 1: Dependency Injection at last

In my last post (read it, its short, honestly), I created an awful Magic8BallSimulator class which violated the Single Responsibility Principle by concerning itself with:
  • Message retrieval
  • Reading user input
  • Printing message and program output
  • Handling user-interactions via a lame read-eval-print (aka REPL) loop
In this post, I'm going to:
  1. Create a class per responsibility
  2. Make the Magic8BallSimulator class depend on those classes
  3. Inject those dependent classes into Magic8BallSimulator (you'll laugh when you see how simple this turns out to be).
  4. and finally, make you sad by exposing a new problem with the code
The new classes broken out are:

MessageService - gets messages
ConsoleInputService - reads user input from the console.
ConsoleOutputService - prints output to the console.
Magic8BallSimulator - Simulates a Magic 8 Ball via a lame read-eval-print (aka REPL) loop.

Oh there's a bit of Dependency Injection - MessageService, ConsoleInputService, and ConsoleOutputService are dependencies of Magic8BallSimulator, which is to say that Magic8BallSimulator cannot do its job of simulating a Magic 8 Ball without them. These dependencies are given to this class (or injected) as simple constructor parameters.
SimulationRunner - Runs the simulator class, and injects new instances of the dependent classes into Magic8BallSimulator.
Hmm, there's still a problem with this code

Let's have one more look at the Magic8BallSimulator constructor.
Notice how we're injecting instances of MessageService, ConsoleInputService, and ConsoleOutputService? What this does is, for example, couple Magic8BallSimulator to ConsoleOutputService which is a specific class printing output in a specific way (to the console). This coupling exposes us to 2 threats:
  1. If we want to change, say, ConsoleOutputService we risk breaking Magic8BallSimulator too, since it depends directly on ConsoleOutputService (and changing the output on the console really shouldn't break our entire application).

  2. If we get a requirements change to alter how we output messages, say, to send them as Instant Messages or via Email, we'll have to rip out ConsoleOutputService and change Magic8BallSimulator as well. This makes our application less maintainable because changes to one section break and cascade to other parts of our application making the whole thing a pain in the ass to work on.
You might be tempted to try something like the code below, don't do it. This is actually worse because it adds another responsibility to Magic8BallSimulator namely that of creating its own dependencies. This actually couples us even tighter to these dependent classes since now we're not only using their specific service-implementations, but instantiating them as well.

Luckily there's a simple way out.

We need to alter Magic8BallSimulator to depend upon abstractions instead of concrete implementations, making Magic8BallSimulator loosely coupled to the classes it depends on.

(Awfully coupled) source code for this post, witness the bad design first hand!

We'll explore this way out in the next post

1 comments:

Anonymous said...

this is so informative...i cant wait for the next post