February 1, 2009

More about dependency injection

In a previous post I talked about problems caused by singletons and how to solve them using dependency injection. I mainly talked about constructor injection. In a reaction to that post Roy Tang voiced his concerns about scaling contructor injection. In real life applications you often have a lot of dependencies, passing them through constructors can become a problem in real life solutions where you’ve got objects with lots of dependencies. Usually when manual IoC does not scale anymore I use an IoC container. But this might not always be an option. In this post I want to talk about a few ways to get around this problem.

Are you solving the right problem?

Often problems like these are indicators of other problems. The first thing to look for when you’ve got classes that have too many dependencies is the single responsibility principle. Classes often have too many dependencies because they do too much. You might want to split up that class in a few more granular classes.

One solution to avoid is reusing dependencies between classes. For example it might be tempting to rewrite this;

   1: class ClassA {
   2:    public ClassA(ILogger logger, ClassB objectB) {
   3:       // ...
   4:    }
   5: }
   6:  
   7: class ClassB {
   8:    public ClassB(ILogger logger) {
   9:       // ...
  10:    }
  11: }

to this:



   1: class ClassA {
   2:    public ClassA(ClassB objectB) {
   3:       // ...
   4:    }
   5: }
   6:  
   7: class ClassB {
   8:    public ILogger Logger {get; private set;}
   9:  
  10:    public ClassB(ILogger logger) {
  11:       Logger = logger;
  12:    }
  13: }

Eliminating ClassA’s dependency on the logger, it can just reuse ClassB’s logger, right? Well no, this breaks the single responsibility principle. The logger reference has become part of the public interface of ClassB effectively giving ClassB an extra responsibility as a LoggerProvider.


Alternatives to constructor injection.


Constructor injection is not always the right way to do things, so you might want to inject your dependencies somewhere else. Property or method injection might be good alternatives.


Property injection exposes dependencies as properties. Property injection is a bit more flexible than constructor injection but also a bit more dangerous. It allows you to set dependencies one at a time, this can be good when you’re testing something that doesn’t need the full set of dependencies reducing the size of your tests. But it can also cause objects to be only partially initialized causing problems. Property injection is very powerful but it gives you a lot of rope to hang yourself with too, be careful using it outside of an IoC container.


If a class only uses a dependency in one method you might want to pass that dependency as a parameter to that method instead of having it passed into the constructor, this is called method injection. For example the following class:



   1: public class Shape {
   2:    public Shape(Canvas canvas) {
   3:       // ...
   4:    }
   5:  
   6:    // ...
   7:  
   8:    public void Draw() {
   9:       // draw to the canvas
  10:    }
  11: }

might be better implemented as:



   1: public class Shape {
   2:    
   3:    // ...   
   4:  
   5:    public void Draw(Canvas canvas) {
   6:       // draw to the canvas
   7:    }
   8: }

Using Defaults


In addition to using different forms of dependency injection when needed you can reduce the amount of code by providing default dependencies. I wouldn’t do this in large projects because adds some coupling but it can be a good way to reduce the amount of “wiring” code and still maintain some testability.


For the ClassA class from the first example would look like this:



   1: class ClassA {
   2:    public ClassA() 
   3:       : this(new LoggerImplementation(), new ClassB())
   4:    { }
   5:  
   6:    public ClassA(ILogger logger, ClassB objectB)
   7: }

Providing you with a two options. You can then use the default constructor in your code and the DI constructor in your tests.


For large applications using an IoC container still gives you the most flexibility with the least trouble. I hope I’ve given a good overview of the options you’ve got for maintaining dependencies when you don’t have access to one of those.

1 comment:

  1. AnonymousMay 29, 2011

    Great post, thanks for this information.

    ReplyDelete