Straight Up
I've stopped using or caring about IoC containers. I used to use them because they were so quick and easy and they kept my code looking pristine and beautiful. Now I do manual dependency injection and the results on non-trivial systems are very interesting and look even more beautiful. At the end of the post we'll examine a little bit more of why I left them behind. In the meantime, I've probably left you wondering what the heck I do to keep things from getting out of hand. "What about the times when you have to inject a dependency through five other objects before it gets to where you need it??" Yeah, we'll get to that.
Focusing the Discussion
The statements I make in this article assume the following:
- You know what an IoC container is.
- You only use an IoC container to clean up your dependency injection.
- You aren't writing prototypical, throw away code.
- The system under question is not an ideal of perfection. It's just a realistic system where I can concretely show the product of applying this abstract idea.
Now, the problem domain: The code in this post was for a Twitter/IM client I was building for a while. Its point was to unify all of your messaging clients into one in a way that made the different clients a non-concern to the user. It was about unifying and simplifying. It has been about a year since I've touched this code so when I first came back and looked at the IoC declaration to re-figure out the lay of the land, I was underwhelmed.
Here's my original IoC container declaration:
The problem I have that needs solving is how to make my code into living documentation that describes itself long after I've forgotten about it.
The Hot New Thang I Replaced IoC With
That code really doesn't tell me anything useful about my system. "Wha- HUH?!" you say. Seriously, I get an idea of what the objects are in my system and how they correlate with my interfaces, but what about how the objects are used by one another? The power of OOP lies in graphs of objects. A graph is nothing if not a precise way of storing the interrelationships between individual elements.
So what's the alternative? Ditching IoC and wiring everything together by hand. Ok, ok. I know. It sounds extreme and it sounds painful. Let's address some of what may seem to be pain. Here we can answer the earlier question of what do you do when you need to inject an object through several layers... you won't need to. The reason you've had to do this in the past is because you instantiated objects within other objects. By giving just one class this responsibility, you prevent that from ever happening again. Just pulling all of the dependencies up to the top most level isn't what this is all about though. There's still pain and, as you can see from this example, it does very little to add to the clarity:
So after looking at the entire system's wire up and revisiting the different classes I came up with some more appropriate naming:
That's the new hawtness. Sitting down, thinking of the code and how the different objects work together and naming them in a way that binds them into a cohesive overarching vision.
One of the key principles of OO design is cohesion afterall. Prior to getting all of these objects together and seeing how they were interconnected I didn't really see that they weren't cohesive. The various objects weren't named in a way that illustrated their cohesion with the rest of the system and I didn't have an easy way of seeing them all related to each other.
A key concept that comes out of this is that code is a form of literature. Donald Knuth says,
"I believe that the time is ripe for significantly better documentation of programs, and that we can best achieve this by considering programs to be works of literature...
Let us change our traditional attitude to the construction of programs: Instead of imagining that our main task is to instruct a computer what to do, let us concentrate rather on explaining to human beings what we want a computer to do.
The practitioner of literate programming can be regarded as an essayist, whose main concern is with exposition and excellence of style. Such an author, with thesaurus in hand, chooses the names of variables carefully and explains what each variable means. He or she strives for a program that is comprehensible because its concepts have been introduced in an order that is best for human understanding, using a mixture of formal and informal methods that reinforce each other."
Donald Knuth. "Literate Programming (1984)" in Literate Programming. CSLI, 1992, pg. 99
By declaring my object graph in a single spot a human can read it, can see how the different objects depend on and relate to one another, I have a great spot to introduce a new programmer into my system. It isn't necessarily easy, but it will be a more thorough and thoughtful treatment on the system than the original IoC declaration. If I had a better job of adhering to DDD principles in this system, I'd like to think this wire up would be even more valuable.
Before you go too long thinking that if I would have just taken the same amount of time with my IoC container I could've fixed the issues with it here's a sample of what it looks like AFTER renaming the classes:
On top of that, even if it did read exactly the same, if I am only using it for dependency injection and you don't believe it will add more value to the system's understanding (and I can't believe that you would argue with that) then it's adding superfluous complexity to my project.
Having a Conversation with Your Code
There may come a time when you try to employ these ideas on a system you're building and it may seem too difficult to get this working. Like writing a book, technical manual, blog post, etc. this technique is an art. The technique itself is not the problem, except of course when it is. To know for sure you need to have a dialog with yourself. Look for the root cause of your difficulties, they'll usually align with places where you've bucked the corner stone OO principles (encapsulation, cohesion, and polymorphism). If, in reading this article, you wonder what about those times when you have an object with 12 parameters that is instantiated inside some other object... ask yourself why you're doing this first, and don't just answer with "Because it was the simplest, easiest thing to do." Simple is not equivalent to the least amount of work. Think towards root cause analysis and solve, or at least move towards solving, the root cause.
In this case, IoC containers never solved the root cause of why my objects were so difficult to instantiate and use. IoC containers never helped me to make a system that better communicated my intent. IoC containers did however help me create a composite application. So I will continue using them to that aim and cease using them for most others.
Good luck with this! Tell me your thoughts and if you think I'm stark raving mad by leaving a comment or shooting me an email. Thanks!