Dependency injection for beginners (like me)

Since starting to practise proper TDD, I started to learn dependency injection too. It's a simple pattern that makes a world of difference. The following are some resources which I found helpful when getting to grips with it:

One thing I found confusing at first (but is now obvious) is how to inject into the dynamic/transient objects created at runtime. The static wiring is simple - with Ninject it's just a single call to kernel.Get<Application>(), and the entire application will be wired up. But when creating an object at runtime, it's not quite so easy. At first I tried to use the same pattern:

class ShoppingCartItem
{
   private ILogger logger;
   private string name;

   public ShoppingCartItem(ILogger logger, string name)
   {
      this.logger = logger;
      this.name = name;
   }
}

class ShoppingCart
{
   private IKernel kernel;

   public ShoppingCart(IKernel kernel)
   {
      this.kernel = kernel;
   }

   public void AddItem(string name)
   {
      ShoppingCartItem item = kernel.Get<ShoppingCartItem>(
         new ConstructorArgument("name", name));
      ...
   }
}

This works, but it is wrong! Ninject will self-inject the kernel to the ShoppingCart, and then inject the ILogger to the ShoppingCartItem. But this is bad for a couple of reasons:

  • The code is now coupled to Ninject. Changing the dependency injection framework now requires all the code which creates objects to be changed.
  • Passing extra non-injected parameters to the ShoppingCartItem constructor is now difficult. It's possible in Ninject, but the syntax is horrible and brittle.
A much better way is to use a factory:

class ShoppingCartItemFactory
{
   private ILogger logger;

   public ShoppingCartItemFactory(ILogger logger)
   {
      this.logger = logger;
   }

   public void CreateItem(string name)
   {
      return new ShoppingCartItem(logger, name);
   }
}

class ShoppingCart
{
   public ShoppingCart(ShoppingCartItemFactory itemFactory)
   {
      this.itemFactory = itemFactory;
   }

   public void AddItem(string name)
   {
      ShoppingCartItem item = itemFactory.CreateItem(name);
      ...
   }
}

Now everything is decoupled, the syntax is clean, and we still have the ability to inject mock items by injecting a different factory implementation.