Wednesday, June 12, 2013

Dependency Injection without IoC Containers

Dependency Injection does not mean using an IoC Container. In fact, Dependency Injection does not even mean using interfaces or base classes. Dependency Injection is all about the code entity (class or method) declaring its dependencies, and asking for them explicitly as arguments to be passed in to the constructor or method. The only alternatives to this are to do object creation inside your code or to use some kind of God object that has a reference to everything else. (This is basically what the service locator does. Sometimes, IoC Containers are used the same way.)

What's the benefit of using Dependency Injection as opposed to one of these alternatives?
1 - It makes for a better api. When you compose your methods and classes this way, you make the dependencies explicit. Your api is more informative. Without Dependency Injection, the api lies to you. It leads you to believe that these dependencies do not exist. To be clear, the dependencies still exist; they are just hidden. You have to look into the body of the method or class to ferret them out. 
2 - Single Responsibility Principle compliance. When your dependencies are not passed in as arguments, your class or method must assume an additional responsibility - that of creating its own collaborators.
3 - You create a seam that gives you a chance to write unit tests against this code.

One key to making this work well is to be explicit in what you ask for. It's the idea embedded in the Law of Demeter. Without going into all the details of the Law of Demeter, the basic idea here is you don't want to traverse the object graph looking for what you want. A silly example comes to mind.

public BadMethod(Car car)
{
FuelInjector injector = car.Engine.FuelInjector;
//code that deals with a fuel injector
}

public GoodMethod(FuelInjector injector)
{
//code that deals with a fuel injector
}

Another good practice is to define your parameters in terms of interfaces or a base class, and let the calling code or test instantiate the correct type. You have to be careful here. You need to be sure to use good object-oriented design, and not just create interfaces willy-nilly. The example I always remember is from John Somnez.

You begin writing your code like this:
class Kangaroo : IKangaroo
{
}
interface IKangaroo
{
}
class BoxingMatch()
{
public BoxingMatch(IKangaroo kangaroo)
{
}
}

I see this all the time. Especially when IoC Containers are being used. This is not the fault of an IoC Container, but people get into a habit of creating an interface for every class without thinking about it more deeply, just so it can participate in the IoC Container. Here's what it looks like if you carry it forward.

class MikeTyson : IMikeTyson
{
}
interface IMikeTyson
{
}
class GeorgeForeman : IGeorgeForeman
{
}
interface IGeorgeForeman
{
}

This way lies madness. The S.O.L.I.D. principles come to our rescue, in this case the Dependency Inversion Principle. You turn the normal way of looking at it on its head. It's the BoxingMatch class that 'owns' or 'publishes' the interface. The BoxingMatch class says, you need to implement the IBoxer interface if you want to work with me. You get something nice like this:

class BoxingMatch()
{
public BoxingMatch(IBoxer boxer)
{
}
}

interface IBoxer
{
}

class Kangaroo : IBoxer
{
}

class MikeTyson : IBoxer
{
}

So, where are we? Dependency Injection exists independently of IoC Containers. The reverse, however, is not true. Whether or not you use an IoC Container, you need to be careful and apply good object-oriented design skills.


No comments:

Post a Comment