5

On my last large project, I used dependency injection really heavily. I started out using constructor injection but even two or three dependencies resulted in really ugly code.

public MyClass(
    IDependency1 dependency1, 
    IDependency2 dependency2
    IDependency3 dependency3)

I am not a fan of property injection. Some people say it implies an "optional" dependency. I think what they mean is that there will be a default dependency (the production class) but it can be replaced with a mock object during test. Many times, my dependencies have dependencies, too, so I can't provide a default value unless I used a singleton or something. My problem with property injection is that I prefer fully initialized objects and don't want to accidentally forget to mock out a dependency during test.

So, I found a compromise. I created a "parameter object" that had properties for all of my dependencies. I passed these to my constructors. This had the benefit of keeping my code somewhat cleaner and I could ignore some dependencies during test if they weren't touched.

Based on feedback I got the impression this was a bad idea. It was constructor injection or parameter injection - no compromise allowed. Later I realized that these parameter objects sometimes became their own classes (or set of classes).

That's when I discovered an interesting pattern. Many business objects depended on other business objects, which had their own dependencies. What I ended up with was an interesting data structure, similar to a linked list.

A Simple Depedency Chain

This data structure could become fairly complex depending on the number of interacting classes. In most cases, the structure was closer to an n-ary tree. Each parameter object could link to any number of other objects (including other business objects). The business objects were the nodes of the tree and the parameter objects were like the edges connecting them.

A Complex Dependency Tree

I think connecting all these objects together without using a declarative dependency injection framework would be mind-numbing. The dependency injection framework I was using was going 8 levels deep in my code building the dependencies from the bottom up in the worst case.

The parameter objects usually just exposed the dependencies. On rare occasions, I would hide them behind friendly function calls and made them their own business objects (usually resulting yet another parameter object). I had to remind myself that the parameter objects only existed because I didn't want to construct dependencies in the constructor (or pass them to the constructor). It only made sense to create new business objects when I started repeating myself. Even then, I ended up with an explosion of parameter objects.

I've read plenty of books explaining that this situation should never happen. Classes should have very few dependencies. It was a sign that my classes did "too much". But in most of these cases, a business object used data from multiple database tables (repositories), configuration settings, services, etc. in order to calculate something. This occurred most often when implementing a series of steps making up a unit of work. When N things are needed to do a task, the best you can do is push those N things to other classes or on up the call stack. Is one class with 6 dependencies better or worse than 3 classes with two dependencies each?

I am hoping to get some feedback on this approach to dependency injection - in how it relates to architecting a business layer.

4
  • 2
    This is where IoC containers come into play. They manage this whole web of dependencies - you just tell them what is needed by who.
    – Oded
    Feb 27, 2013 at 21:04
  • 1
    Some people say it implies an "optional" dependency. I think what they mean is that there will be a default dependency (the production class) but it can be replaced with a mock object during test. -- No. Feb 27, 2013 at 21:04
  • I prefer fully initialized objects and don't want to accidentally forget to mock out a dependency during test. -- Yes. Feb 27, 2013 at 21:05
  • @Oded - I used Ninject for managing my dependencies. Feb 27, 2013 at 21:09

2 Answers 2

3

IMHO creation of "complete" classes via constructor has always been appealing, however setter injection requires a no-arg constructor, allowing pre-conditions (e.g. assertions) to be a reasonable alternative to guarantee "completeness" in the context of DI containers.

To me it is a trade-off between adding complexity versus implementing a simple, pragmatic, solution. And simplicity always wins.

1

To be perfectly honest, the intermediary "Parameter" classes seem like a bit of over-engineering here. You're introducing code that deepens the object graph and actually hides the dependencies.

When using a container you can set up your environment so that whatever object you need is actually created by the container with all of it's dependencies. What I usually do is set up the infrastructure or framework so that whenever I'm in the actual application code, all my dependencies are already set. I'll set up my testing environment in the same way, so that there's no way I'll forget a dependency.

When using a framework like Asp.Net MVC, this means setting the framework up so that each controller is actually resolved by the container. The controllers here are the entry point to the application. Since the controllers will have dependencies to various repositories and services, the container will also resolve those and their dependencies, however deep the graph is.

This means that, if I'm careful not to blindly instantiate a service or repository I'll always have objects which are completely ready to do their work, whether the dependencies are set via constructors or setters.

With some planning, the app layer (controllers/viewModels/views) and the business layer (repositories/services) can live within an infrastructure where you never have to instantiate a service using the new operator, or even resolve it using the container (which is better, but if not used carefully can lead to problems, where you introduce unnecessary dependencies to the container itself).

About setter vs constructor injection, I disagree that setter injection implies optional dependencies. They are dependencies just like the rest, and are very useful when you have a hierarchy of classes in which constructor injection would mean dragging parameters through multiple levels of constructors until finally setting the dependency in some base class. Of course, they have their trade-offs, specifically, that with setter injection, you have to be careful with your constructor logic, which IMO, should be kept to a minimum, outside of setting up the object.

7
  • Could you elaborate on how you represent your dependencies? Do you use parameter injection? Ninject does a good job of initializing my parameter objects. But you're right... they add a lot of depth. Feb 28, 2013 at 1:01
  • I don't do anything special, I just set them up as constructor/setter parameters. Feb 28, 2013 at 9:13
  • I see. So you are more or less describing an IoC container. Feb 28, 2013 at 12:35
  • Yes, the point I was trying to get across is that I let the container worry about the dependencies, and just set up the dependencies as constructor/setter params, no matter how many of them. Because I (possibly wrongly) got the idea that you were doing some sort of manual DI (even when using Ninject), so the number of constructor params was getting too big, and setter params are tricky because a developer can forget to set those. The DI container should never forget to resolve a dependency (it might fail trying, though) Feb 28, 2013 at 13:04
  • Ninject is doing all the heavy lifting for me. I think this would be insanely tedious to do manually. On an side note, I avoided using the Inject attribute on my parameter objects by creating a helper method for Ninject. All it does is look at the types of all of the parameters and set those it finds a binding for. Feb 28, 2013 at 13:24

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.