The magic of DI container is to avoid doing a very tiresome manual binding of all your objects. Binding that will need to be updating every time a single thing change.
Whatever is made of your DI container (service locator pattern inside) is not your problem. What matter to you is that it works and you need to explicitly reference the service locator or whatever he's using inside most of the time (except special cases as always).
for instance in Java/Spring I would have :
@Component
public class XX implements IMyInterface{
@Autowired
public IMyBean injected;
@Transactional
public void save(MyObject toSave){...}
}
Here :
- @Component indicate to the framework to register this class, by default it will be instantiate only once and as such it must have no state or one that does not change once initialization is done. In the case of Spring, you can eventually control the scope of the instantiation, for instance you can tied it to the current thread, of the current HTTP user session.
- @Autowired : indicate that I want the DI container to inject a instance that implements the interface IMyBean. If there are more than one candidate i will need to add extra information to help my DI container to resolve it.
- @transactional : here we are outside of just the DI Containers context, here the framework will automatically open a database transaction for me if one is not already active and commit it after the transaciton (or rollback if there is an exception).
As such we can see that :
- I don't need to maintain one big main where I initialized everything by hand. Let alone multiple of them if I have a need for. I might still need a main but it will be lighter.
- By making everything singleton by default, it will avoid multiple instantiation of the same thing if it is not needed
- A framework can do more than just inject dependency, it can even read annotation in order to avoid classic boilerplate code (get a connection from the pool, open transaction, commit/rollback, release the connection), allowing me to focus on what I want to do.
- Instead of making a public class, I could have make it package private, so I would only expose my interface (my API) to the code than need to use implementation of the said interface.
- Standardization : by making every library you might need usable the same way, it's easier to grasp the next dependency you will need to use and that your framework will manage for you.
- You can also have some way to detect new dependencies and integrate them at runtime (for instance if multiple team works on same large project).
Some of the cons when you start are the following :
- Cyclic dependecy will make your code crash at runtime and might be not easy to solve (generally it is a design proble).
- Without a solid grasp of the basics, you will be lost of what is the framework doing for you and why sometimes it behave like it is (for instance, knowing what is a transaction/commit/rollback for relational database). For instance authentication and security isn't an esy topic when you need something solid, so understanding how the framework is doing it for you is also not easy.
- When it does not work for some reason (configuration, dependencies version problem) it might take some effort to resolve.