As you have seen in my previous blog Refactoring to Dependency Injection Principle , the ProductService class is still tied to the concrete implementation of the ProductRespository because it’s currently the job of the ProductService class to create the instance.
This can be seen in the ProductService class constructor (shown in the red box).
Dependency Injection can move the responsibility of creating the ProductRespository class implementation out of the ProductService Class by having it injected via the class’s constructor.
By removing the responsibility out of the ProductService, we achieve the following
- This enables to substitute to be passed to the ProductService class during testing, which enables you to test the ProductService class in isolation.
- It ensures that the ProductService class adhere to the Single Responsibility principle; it is now only concerned with the coordinating of retrieving data from the cache or repository and not the concrete implementation.
Let’s see the definition of Dependency injection (DI)
Dependency injection (DI) is a technique for achieving loose coupling between objects and their collaborators, or dependencies. Dependency Injection (DI), also more cryptically known as “Inversion of Control” (IoC). There are two primary approaches to implementing DI: constructor injection and setter injection.
Constructor Injection is the DI technique of passing an object’s dependencies to its constructor.
Setter Injection does not force dependencies to be passed to the constructor. Instead, the dependencies are set onto public properties exposed by the object in need. As implied previously, the primary motivators for doing this include:
- Supporting dependency injection without having to modify the constructor of a legacy class, and
- Allowing expensive resources or services to be created as late as possible and only when needed.
Dependency Injection Benefits
There are several benefits from using dependency injection containers rather than having components satisfy their own dependencies. Some of these benefits are:
- Reduced Dependencies
- Reduced Dependency Carrying
- More Reusable Code
- More Testable Code
- More Readable Code
When to use Dependency Injection
Dependency injection is effective in these situations:
- You need to inject configuration data into one or more components.
- You need to inject the same dependency into multiple components.
- You need to inject different implementations of the same dependency.
- You need to inject the same implementation in different configurations.
- You need some of the services provided by the container.
When You Shouldn’t Use Dependency Injection
Dependency injection is not a silver bullet. There are reasons for not using it in your application, some of which are summarised in this section.
- Dependency injection can be overkill in a small application, introducing additional complexity and requirements that are not appropriate or useful.
- In a large application, it can make it harder to understand the code and what is going on because things happen in other places that you can’t immediately see, and yet they can fundamentally affect the bit of code you are trying to read. There are also the practical difficulties of browsing code like trying to find out what a typical implementation of the interface actually does. This is particularly relevant to junior developers and developers who are new to the code base or new to dependency injection.
- You need to carefully consider if and how to introduce dependency injection into a legacy application that was not built with inversion of control in mind. Dependency injection promotes a specific style of layering and decoupling in a system that may pose challenges if you try to adapt an existing application, especially with an inexperienced team.
- Dependency injection is far less important in functional as opposed to object-oriented programming. Functional programming is becoming a more common approach when testability, fault recovery, and parallelism are key requirements.
- Type registration and resolving do incur a run-time penalty: very negligible for resolving, but more so for registration. However, the registration should only happen once.
Please feel free to comment your opinion about this article or whatever you feel like telling me. Also if you like this article, don’t forget to share this article with your friends. Thanks!
Hi,
Thanks for the blog post. Just a few small notes from my side..
Despite of readability, Di principles are very welcome in larger applications. when you say “harder to understand the code”, it’s harder compared to what? Spaghetti and lasagna style code might not be able to read much easier either 🙂
“Type registration and resolving do incur a run-time penalty”. Do you mean heavy dependencies that might take ages to register and resolve? If so, then there is something wrong with those dependencies. If you still need to handle and use those dependencies, you would need to review some of the lazy resolution patterns.
Btw, property dependency injection pattern is only applicable if you do have local default of that dependency and want to give possibility to override it for the consumers. why would you not want to change constructor for legacy class? is it so, that you might end with more fragile object, that works if property is set, and might blow up, if something is not set before its usage?
And property injection is not correct way to deal with expensive dependencies. For that – you might look at lazy evaluation pattern.