The key of DI containers is the abstraction. DI containers abstract this concern for you. As you may guess, it has a noticeable impact on the code, often translated into a fewer number of constructors, setters, factories and builders. In consequence, they make your code more loosely coupled (due to the underlying IoC), cleaner and simpler.
Backgrounds
Years ago, DI required us to write config files (declarative programming). It was fantastic to see the number of LOC diminishing substantially, but while the LOCSs decreased, the number of config files slightly increased. For medium or small projects it was not a problem at all, but for larger projects having the DI scattered between code and several files became a pain in the ...
Nonetheless, the main problem was the paradigm itself. It was quite inflexible because the descriptors left little space for customizations.
The paradigm shifted progressively towards to convention over configuration, which trades config files for annotations or attributes. It keeps our code cleaner and simpler while providing us with the flexibility that XML could not. Some DI contianers are programable so the DI graph can change in runtime.
Now, without setters, constructors and XML the application is more than clean. Is also magic.
On the down side...
Convention over configuration makes the abstraction so heavy that you barely know how it works. Sometimes seems magic and here comes one more drawback. Once is working, many developers don't care about how it works.
This is a handicap for those who learnt DI with DI containers. They don't fully understand the relevance of the design patterns, the good practices and the principles that were abstracted by the container. I have asked developers if they were familiarised with DI and its advantages and they answered: -Yes, I have used Spring IoC-. (What the hell does it mean?!?)
One can agree or not with each of these "drawbacks". In my humble opinion, it's a must to know what's going on in your project. Otherwise, we don't have a full understanding of it. Also is to know what DI is for and to have a notion of how to implement it is a plus to consider.
On the plus side...
One word productivity. Frameworks let us focus on what really matters. The application's business.
Despite the commented shortcomings, for many of us (which job is conditioned by deadlines and costs), these tools are an invaluable resource. In my opinion, this should be the main argument in favour of implementing a DI containers.
Testing
Whether we implement pure DI or containers, testing is not going to be a problem. Quite the opposite. The same containers often provide us with the mocks for unit testing out of the box. Along the years I have used my tests as thermometers. They tell me if I have relied heavily on the containers facilities. I say this because the containers I'm used can initialize private attributes without setters or constructors.
Sounds tempting right? Be careful! Don't fall into the trap!
Suggestions
If you decide to implement containers, I strongly suggest sticking with the good practices. Keep implementing constructors, setters and interfaces. Enforce the framework/container to use them. It will make easier possible migrations to another framework or to remove the actual one. It can reduce significantly the dependency on the container what ease the testing too.
Regarding this practices:
When to use DI containers
My answer is going to be biased by own experience which is mainly in favour of DI containers (as I commented, I'm heavily focused on productivity, so I have never had the need of pure DI. Quite the opposite).
I think you will find interesting Mark Seeman's article about this subject, which also answers the question how to implement pure DI?.
Finally, if we were speaking of Java, I would consider first taking a look to the JSR330 - Dependency Injection.
Summarising
Benefits:
- Cost-cutting and time-saving. Productivity.
- A smaller number of components.
- The code becomes simpler and cleaner.
Drawbacks:
- DI is delegated to 3rd party libs. We should be aware of their trade-offs, strengths and weakness.
- Learning curve.
- They quickly make you forget some good habits.
- Increase of the project's dependencies (more libs)
- Containers based on reflection require more resources (memory). This is important if we are constrained by the resources.
Differences with pure DI:
- Less code and more configuration files or annotations.
Do I really need a factory method or class for each class?
Since you are creating instances of "Bar" and "Quz" it makes sense to use factories to decouple them.
Yet, you are only creating a Bar
in the constructor, so you could not take a factory for it, just take an instance of Bar
instead. Doing so would be an example of dependency injection without a factory; it allows the client code provide the object… using a factory if that is the intention, or not. In fact, you are not using Bar
, you can skip that.
Edit: I notice that you want to use the result of calc
to create the Bar
. Taking the factory instead of the Bar
instance would give you a better encapsulation as it ensures that the Bar
was created with a valid result for Quz
given the min
and max
provided... yet, bar
is public, and the client code could overwrite it which defies that protection.
As per Quz
, since you will be creating a new instance each time the client code calls calc
, you will need the factory to be able to get more instances.
Do I indeed need the init() function to replace the constructor?
You can take the factories in the constructor. Using a separate method creates a problem because it allows the developer to call the constructor and skip calling init
.
Now, if you wanted to hide the factories and have the client code create multiple instances that share the same factories... well, you could create a factory for that. I have to say that there is no need to do that.
Or am I doing it terribly wrong?
Separating init
from the constructor. Consider what happens if the client code creates an instance and calls calc
without calling init
: In your code the random number will either be 0 (or whatever default value) or trash because your field weren't initialized (depending on your language).
It could have been worst, if you actually used Bar
, it wouldn't have been initialized because you do that in init
, so it would be an invalid pointer.
Best Answer
Use dependency injection, but whenever your constructor argument lists become too big, refactor it using a Facade Service. The idea is to group some of the constructor arguments together, introducing a new abstraction.
For example, you could introduce a new type
SessionEnvironment
encapsulating aDBSessionProvider
, theUserSession
and the loadedDescriptions
. To know which abstractions make sense most, however, one has to know the details of your program.A similar question was already asked here on SO.