Refactoring large constructor injection dependency lists.
Dependency injection is a valuable tool for a software engineer. Dependency injection allows for loose coupling between services in your system and often drives a good maintainable, testable design.
Constructor injection is one way of injecting dependencies into your class. It’s often the most used and best way to get dependencies into your class. There are other ways such as method injection, which is more suited to injecting different implementations of your dependency at runtime and property injection which is more suited to classes that already have concrete dependencies inside them and allows dependency injection without changing the existing implementation details.
Constructor injection is considered the best method as it enforces a good design from the start. Missing dependencies will immediately become apparent when the application first runs.
In a .NET application, the composition root will often be the built-in dependency injection container where we register all out dependencies adding them to the IServiceCollection container. This will ensure all registered dependencies will be injected into classes at construction time and allows each class to “ask for” the dependencies it needs.
So having established constructor injection as the best method, I want to examine what happens when the dependency list grows too large. As stated by Mark Seeman and Steven Van Deursen in their Dependency Injection book, typically more than about 4 dependencies tends to indicate a code smell that points to the violation of the Single Responsibility Principle (SRP). The thinking behind this is that when the dependency list grows large, the class using those dependencies will be taking on too much responsibility as it needs all those dependencies to carry out its work.
Sometimes you can get around this by looking for functionality in the dependencies that sit together and carry out functionality that is closely related.
The following is an example from a code base I look after.
The above dependency list is too large. I have highlighted two dependencies that implement data access. The data layer in this application splits data querying from data updates (commands). These dependencies work in the same class type and typically the class that implements the functionality implements both IQueryFactory and ICommandFactory interfaces.
Both these interfaces can be combined into one interface
The combined interface can now be injected into the class, reducing two dependencies to one.
领英推荐
Another method of refactoring dependencies is looking at related functionality and creating a Fa?ade around that functionality. As outlined by Gamma et al. in their Design Patterns book, a Fa?ade facilitates the encapsulation of subsystems into a smaller more usable interface.
For example, in the dependency list for the above class there are a number of dependencies related to sending out responses. This is business logic that is based around sending out responses from the information being handled in the service.
All the dependencies related to sending responses could be grouped into a fa?ade class that is specifically related to sending responses. Its job is to provide a simple interface the client can use to handle sending out responses. The constructor of that fa?ade looks like this:
The TransactionResponseService is essentially a Fa?ade class that encapsulates the sub systems related to sending responses, such as dealing with message queues, adapting object models and updating data sources.
The dependency list has now been reduced by removing 5 dependencies and replacing it with one.
There are other strategies for dealing with large dependency lists. The key point is to recognise a large amount of dependencies as a code smell and use it as an indicator that you should probably take a look at why that particular class needs so many dependencies and understand that this may point you to a more testable, maintainable design.
References
Gamma, et al. (1995). Design patterns: elements of reusable object-oriented software.?
Mark Seemann, Steven van Deursen. (2019) Dependency Injection. Principles, Practices and Patterns.