Dependency Injection

This article provides a short and focused introduction to dependency injection, explains its advantages, how an inversion-of-control container works and why you’d want to use it.

I’m going to assume that you already have a good understanding of Unit Testing. You should already understand that Unit Testing is as much about design as it is about finding bugs – designing something to be testable means making it simple to construct and abstracting its dependencies on other components to make it simple to isolate.

In the beginning, there was a…

Big Ball of Mud

If you have worked on a complex piece of software, you might envision representing the entire world in a neat class hierarchy, so the user of your code could write this:

void changeTire(Car myCar, int tireIndex, Tire newTire) {
  Wheelnut[] removedNuts = myCar.Suspension[tireIndex].Wheelnuts.TakeOff();
  
  myCar.Suspension[tireIndex].RemoveTire();
  myCar.Suspension[tireIndex].InstallTire(newTire);
  
  myCar.Suspension[tireIndex].Wheelnuts.AddRange(removedNuts);
}

This might appear like a great idea from the user’s point of view, but when you go ahead to implement this, you will end up with lots of classes depending on each other. Possibly you want to send notifications around in this hierarchy, eg. when the car corners, the bodywork calculates the weight distribution from the G forces, notifies the suspension and the suspension hands over the load to the wheels. If the a wheel bursts, if notifies the suspension, which notifies the bodywork…

So in the end, you’ll have a monstrosity that possibly takes minutes of manual interaction to just bring into the state you want to check in the debugger. You’ll lose track of what notifies what, forget your own ideas about how the design has to fit together, send notifications in the wrong order or break contract by forgetting a certain notification or side effect.

Testing could have avoided that! If you wanted to write such a thing with Unit Testing in mind, you’ll either quickly come to the conclusion that “Unit Testing is a thing for purists that you cannot apply here and will look into later” or, better, discover that your classes are hard to use, tightly coupled and even harder to test in isolation (remember, Unit Testing is a about testing the smallest units of your code, not how the entire project works – that’s Integration Testing!)

Service Hell

Another idea you might come up with is to wrap up your components as services in the style of a lightweight service oriented infrastructure:

void changeTire(IServiceProvider serviceProvider, int tireIndex, Tire newTire) {
  ISuspensionService suspensionService = (ISuspensionService)serviceProvider.GetService(
    typeof(ISuspensionService))
  );
  
  Debug.Assert(suspensionService.CanDetachTires);
  Debug.Assert(tireIndex < suspensionService.TireMounts.Count);
  
  object removedParts[] suspensionService.TireMounts[tireIndex].Unlodge();
  
  suspensionService.TireMounts[tireIndex].RemoveTire();
  suspensionService.TireMounts[tireIndex].InstallTire(newTire);
  
  suspensionService.TireMounts[tireIndex].RelodgeWith(removedParts);
}

These lightweight services work out well in small scale where the interactions are obvious and the number of general-purpose services accessed across the board stays low. Unit testing is done by calling the function with a service provider that contains mocked versions of the dependencies.

Mocking is the technique of creating place holder objects that implement minimal functionality to make the tested method believe it’s interacting with the real thing. Mock objects can contain some assertions as well – in the above example, the mocked ISuspensionService could make sure that the Unlodge() method has been called before RemoveTire() is used.

Now as the system grows in complexity, testability quickly goes out of the window because you can’t see which services a component requires (you’re only handing it a IServiceProvider) and components typically interact with a dozen or so of services.

class RandomServiceConsumer {

  public RandomServiceConsumer(ServiceContainer services) {
    this.services = services;
  }
  
  public void DoSomething() {
    IFooService foo = (IFooService)this.services.GetService(typeof(IFooService));
    IBarService bar = (IBarService)this.services.GetService(typeof(IBarService));
    
    foo.Fooicate(bar.Limit);
    // ...
  }
  
  // ...
  
}

So even if the developers were disciplined enough to write concise and well-defined interfaces all the way, never accessing an implementation directly and clearly defining all dependencies, the result will eventually be chaos. Testing becomes a nightmare and the design tends to disintegrate.

Dependency Injection

But fear not, for this problem is one that has been solved already. The solution is called Dependency Injection, a name that comes from the fact that your component no longer goes shopping for dependencies in your service container, but gets them handed to it instead:

class ServiceConsumer {

  public ServiceConsumer(IFooService foo, IBarService bar) {
    this.foo = foo;
    this.bar = bar;
  }

  public void DoSomething() {
    foo.Fooicate(bar.Limit);
    // ...
  }
  
  // ...
  
}

This solves the problem of testability and clearly communicates the services any given component needs.

However, you might be asking, why should this be any better than the service container concept? After all, you’ll end up with a big mess of initialization code handing references all over the place to wire the components to each other. And where should all the references be stored to keep the service providers alive when they’re not needed?

Enter…

Inversion of Control

An Inversion of Control container is the missing link to make Dependency Injection work for you. This container will store your service providers and manage their lifetime.

// Component which provides the FooService
class FooProvider : IFooService {}

// Another Component, provides the BarService, requires FooService
class BarProvider : IBarService {
  BarProvider(IFooService fooService) {}
}

// Yet another Component, requires FooService and BarService
class ServiceConsumer {
  public ServiceConsumer(IFooService foo, IBarService bar) {}
}

void Main() {
  Kernel myKernel = new Kernel();
  
  myKernel.Bind<IFooService>.To<FooService>();
  myKernel.Bind<IBarService>.To<BarService>();
  myKernel.Bind<ServiceConsumer>.ToSelf();

  ServiceConsumer consumer = myKernel.Get<ServiceConsumer>();
}

The Inversion of Control container, named kernel in the example, will automatically resolve the references for you. When it sees a request for an instance of the ServiceConsumer class, it will establish that the services IFooService and IBarService are required and create instances of them as well.

This makes initialization order issues a thing of the past.

Depending on how your container is configured, it could store the service instances and reuse them when another service requires the IFooService or IBarService. Or it could create a new instance each time. Or it could provide you with a separate instance per thread. The point is, you can configure all this without changing the services themselves.

You literally get the best of everything:

  • Service providers and consumers do not depend on a service framework, so you can write services and providers in isolation or even use existing classes as service providers without having to write adapter code for a service framework.
  • Dependencies of any component can be clearly seen in its constructor. Component initialization order is no longer something for a guru to figure out but becomes computable and can be resolved automatically.
  • Adding a dependency is still as easy as with the service container concept. Just add the required service to the constructor and the IoC container will provide it to you as well as adjust the component initialization order if neccessary.
  • Testability is guaranteed since all dependencies can easily be provided for in unit tests. Furthermore, the unit tests do not depend on the service framework and can directly work on the components.
  • Discourages big consumers interacting with dozens of services. You can write them and it won’t be any harder than before, but it will be obvious when a component interacts with too many services.
  • Dependency management becomes configurable. You can, like in the previous code snippet, set up dependencies in code with type safety, but you can also set up dependencies from an XML file or through a plugin system, thereby killing any references between assemblies — all without having to modify a single line in the service providers or consumers.
  • Problems like unsolvable initialization order (circular dependencies) can be reported to you in clear English error messages instead of being found out after rearranging the component initialization code 10 times and sweating over the debugger display.
  • Lifetime management of service providers is no longer a source for unnecessary complexity. The service container can be told whether to keep a service provider as singleton, whether to create one per thread or whatever else you might require.

In short, all your dependency problems get solved, your service providers can directly be used like any simple object outside of the service framework, Unit Testing is as easy as it was without services and you can eliminate many assembly interdependencies.

Writing a service container as described here, one that automatically determines initialization order, supports configurable object lifetime management and does all the hard work is luckily not something you need to do. There is already a number of .NET IoC containers out there to choose from:

This article is going to concentrate on one of these:

Ninject

Ninject is my IoC container of choice because it is very well designed, lightweight and versatile. It has an intuitive configuration language using a fluent interface and it is one of the few containers that can be used on the .NET Compact Framework or on the XBox 360 (with XNA). And it has ninjas.

So without further ado, let’s convert the last pseudo-code snippet into working code that you can run and fiddle around with:

namespace Demo {

  /// <summary>Example service offering 'fooication' to its users</summary>
  public interface IFooService {

    /// <summary>Fooicates some value, whatever that is</summary>
    /// <param name="someValue">Value that will be fooicated</param>
    void Fooicate(int someValue);

  }

  /// <summary>Example service providing some limit</summary>
  public interface IBarService {

    /// <summary>Some Limit. For something. Or so</summary>
    int Limit { get; }

  }

  /// <summary>Default implementation of the fooication service</summary>
  public class DefaultFooService : IFooService {

    /// <summary>Initializes a new fooication service</summary>
    /// <param name="barService">
    ///   Bar services that provides the limit for the fooication
    /// </param>
    public DefaultFooService(IBarService barService) {
      this.barService = barService;
    }

    /// <summary>Fooicates some value</summary>
    /// <param name="someValue">Value that will be fooicated</param>
    public void Fooicate(int someValue) {
      int fooicated = Math.Min(someValue, this.barService.Limit);
      Console.WriteLine("Fooicated is {0}", fooicated);
    }

    /// <summary>Bar service controlling the fooication limit</summary>
    private IBarService barService;

  }

  /// <summary>Default implementation of the fooication service</summary>
  public class BarService : IBarService {

    /// <summary>Some Limit.</summary>
    public int Limit { get { return 42; } }

  }

  /// <summary>Example service consumer</summary>
  public class ServiceConsumer {

    /// <summary>Initializes the example service consumer</summary>
    /// <param name="fooService">Foo service that will be used</param>
    /// <param name="barService">Bar service that will be used</param>
    public ServiceConsumer(IFooService fooService, IBarService barService) {
      this.fooService = fooService;
      this.barService = barService;
    }

    /// <summary>Runs the test</summary>
    public void Test() {
      Console.WriteLine("Limit is {0}", this.barService.Limit);
      this.fooService.Fooicate(42 * 2);
    }

    /// <summary>Foo service used by the example consumer</summary>
    private IFooService fooService;
    /// <summary>Bar service used by the example consumer</summary>
    private IBarService barService;

  }

  /// <summary>Service bindings for the our test case</summary>
  public class MyModule : StandardModule {

    /// <summary>Binds the services to their providers</summary>
    public override void Load() {
      Bind<IFooService>().To<DefaultFooService>();
      Bind<IBarService>().To<BarService>();
      Bind<ServiceConsumer>().ToSelf();
    }

  }

  /// <summary>Contains the program startup code</summary>
  public class Program {

    /// <summary>Main entry point for the application</summary>
    static void Main() {
      using (IKernel kernel = createKernel()) {
        ServiceConsumer consumer = kernel.Get<ServiceConsumer>();
        consumer.Test();
      }
    }

    /// <summary>Creates the Inversion of Control container</summary>
    /// <returns>A new Inversion of Control container</returns>
    private static IKernel createKernel() {
      return new StandardKernel(
        new MyModule()
      );
    }

  }

} // namespace Demo

That’s it. Try to use IoC/DI in one of your projects and find out what you can do with the IoC container you chose. It will take some time to get used to this style of writing software, but it will pay off very quickly because it’s faster, cleaner and easier than manually managing your dependencies. It won’t take long before you get disgusted just looking at tightly coupled code. Loose coupling rules! =)

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

Please copy the string 6uXriX to the field below:

Social Widgets powered by AB-WebLog.com.