The way I see it, there are three reasons for using an Inversion of Control (IoC) / Dependency Injection (DI) container:
- To decouple the actual implementation from a base class/interface (the contract), so that we can change the implementation when needed
- To allow the implementation itself to have services injected into it when it is built
- To control the lifetime of a service, for example, singleton, web request scoped, transient, etc
For #1, one common approach is to create an interface for every service that we are registering in the IoC/DI – this is actually why we call it Inversion of Control: we don’t control it, the container does. Nothing against it, but it may not be necessary: if for the service we are registering there will never, ever, be a different implementation (and we know that this may happen, like, for infrastructure code), there isn’t really a need for registering a service under an umbrella interface (or base class). Of course, this may bite us later, so we need to be really careful about it. The way I sometimes avoid interfaces is because I really don’t need them, and I end up saving some extra code and files.
The second reason is straightforward: we declare, on the implementation’s constructor, the services that we will be taking as dependencies, so that we can keep a reference to them for later usage.
Finally, reason #3, for me, its, for example, what makes Singleton an anti-pattern. Using an IoC container we can control the lifetime and have a service iinjected into our classes, transparently, without caring if there is one or more than one instances of it.