What's all the fuss about IoC containers?
What the heck?
Dude. I'm with ya.
Some time back ago, I was running around asking the same question to several of my friends, who were container fans.
Don't get me wrong. I like IoC (or, more accurately, Dependency Injection). I've been doing dependency injection for my objects for years without knowing this fancy name.
To me, the virtue of IoC is simple and straight: it honors my laziness.
When designing classes, I just ask for what I need, never worry about where and how I can get the darn thing. As long as I can get my class completed, I'm out playing games.
That's IoC. IoC is just that you can write a pretty printer without thinking where it is printing to; it is that you can kiss a Kissable without knowing this is a girl or your pet dog.
Let's say we have a PrettyPrinter class:
This is already an IoC class that expects the dependency on output being injected.
I do this kind of coding millions of times.
When it comes to create a PrettyPrinter object, I just create it:
or, to print it to the standard error:
or, to print it to a file:
or, to print it to nowhere:
or, to get the Output object from a singleton:
or, to get the Output object from a pool:
blah blah blah.
Handy, easy, straight-forward, why do I need a container for this kind of easy thing?
Is that it?
My container friends did give me answers before they were tired of my solicitation.
They used Pico Container as examples as Pico is one of the most famous light-weight IoC containers.
Using Pico, you should change your PrettyPrinter to:
One friend told me this. (By the way, Pico team confirmed my doubt just recently, such use of pico is never recommended.)
And I asked:
Why? Why should I do this? Is there any benefit? An additional dependency to the Pico API?
Another friend's point made me a little less confused:
The PrettyPrinter class doesn't need to be changed. But you can change your code that creates PrettyPrinter objects so that you don't have to explicitly call the constructor to lace things up together:
And I thought:
Looks better. But I still don't get it.
Why is it a big deal to explicitly call the constructors and explicitly inject the dependencies?
Isn't that the most straight-forward?
Why is it better to rely on an proprietary API and use the ugly down-cast to create my objects?
Why are implicit and invisible dependencies better than explicit and visible ones?
With Pico Container, you can dynamically change your dependencies at runtime.
Confused as ever, I kept bugging him:
But, is that so important that I should dump my natural and straight-forward old plain way of creating objects? Is it worth the trouble and pain of losing type safety?
And the conversation ended there.
Is IoC container any good?
Pico Container gives some explanations about the why IoC container. After reading it again and again, I seemed to get some better idea. But I was not sure if I really understood what it's trying to say.
Then I set out to create my own IoC container: Yan.
This experience clarified things a lot to me.
Is an IoC Container any good?
Yes. It can help in the following aspects:
- When the number of objects to be laced up scales up, it may start becoming unwieldy to do it in my old plain way of creating objects.
- An IoC container enables configuration of dependencies at runtime.
- An IoC container typically supports auto-wiring, where dependencies are not explicitly declared, but figured out automatically by the container.
This at the first glance looks scary because one may lose control and knowledge about how a complex system gets configured. But, in simpler senario, it does have the benefit of saving us some tedious work of specifying dependencies.
- An IoC container may have features such as declarative singleton, life cycle management, aspect orientation, object pooling, object monitoring, etc.
Yan container, utilizing combinatory logic, allows flexibly reusing and combining simpler components to create more complex components.
So, when you use an IoC Container, you get all these free of charge.
Is IoC container any bad?
- Object creation using IoC Container is not straight-forward and typically more complex. How do you compare new A(new B()) with
- Type safety cannot be preserved in a container. One has to perform the dangerous and ugly downcast to obtain an instance from the container.
- Misuse of auto-wiring can easily lead to an unpredictable system where no one knows what component is assigned to what other component.
- People are tempted to use IoC container as a super factory, similar to what my first friend told me. And that is totally against the principle of IoC, where you do not go find dependency for yourself, no matter this is via a static factory, a constructor, or an IoC container.
- A proprietary API needs to be brought into the game.
When to use IoC container?
There's no silver bullet. IoC container is no exception. It is just a tool that can be useful in certain senario.
What we have to remember is: use the right tool for the right job.
From the above analysis of pros and cons, this is what I finally came up with:
- First of all, and the rule of thumb: do not depend on any IoC container API in your IoC objects. It is a tragedy to violate the principle of IoC by using an IoC container. IoC container is for the code that assembly objects together; it is for the configuration of the system. After all, it is not for the business objects.
- IoC container is typically responsible for configuring and lacing up business objects at the top level. It is up to the discretion of the programmer or the architect to decide whether an IoC container or the plain old "new" should be used to put things together. Some factors to think about when making this decision are:
- Is the number of objects to assembly big?
- Is auto-wiring desired?
- Is any of declarative singleton, life cycle management, object monitoring etc. necessary?
- There are still many senarios to use the plain old "new" or a factory. For simple object creation with no requirement for dynamic configurability or any of the additional services provided by IoC container, why bother?
So, what makes a good IoC container?
A good IoC container, IMHO, should have the following characteristics:
- No intrusion. The goal is to allow the programmers for business objects to be completely ignorant of the container. It has two aspects:
- The most obvious one, container should not require business objects assembled by it to implement any interface, to inherit any class, to call any API. This avoids direct dependency on the container.
- The container should not require business objects to conform to any coding convention, such as "you have to expose public constructor", "you have to expose Java Bean setters", "you have to have a method named like injectXXX", "you have to use a special annotation" etc. Such restriction places an implicit dependency on the container because the programmers of the business objects still have to be aware of the do's and dont's from the container.
- Programmability. For many applications, being able to configure the application using xml makes the most sense. However, it is sometimes desirable to be able to configure the container via a programming language. Or, we may want to be able to configure the application using both xml and a scripting language. In these cases, having a programming interface is a big plus. Indeed, it is relatively easy to build a xml/script configuration support on top of the programming inteface, but not vice versa.
- Support for services such as lifecycle management, declarative singleton etc.
- Declarative API is preferable. It is nice to expose a declarative API rather than one that requires procedural coding.