After some debates about using dependency injection versus hiding implementation details (a.k.a. abstraction) which came up during code reviews, I thought about writing some opinion on these two approaches which seem to be opposing each other.
Dependency injection is the norm in modern programming, but people seem to apply it blindly without taking into consideration other perspectives. Some use cases for dependency injection:
- helps with testing code (huge positive - maybe the biggest positive thing for this approach) - passing dependencies from top to bottom makes mocking during unit tests really easy
- spot design mistakes earlier - too many dependencies passed to one class might mean a design issue
- separation of components - dependencies should be on abstract not concrete classes
An opposing perspective to passing dependencies from top to bottom in a class hierarchy is hiding implementation details or abstraction. Some use cases would be:
- building a library which should be used by clients who should not have knowledge of the implementation - in this case there is no reason to expose all the internal dependencies
- no need to test by mocking a certain dependency - yes, it is possible that sometimes it makes sense not to test some scenarios which refer to OS classes, maybe related to IO or networking, in which case, those classes don’t need to be wrapped and passed as dependencies and then mocked - it would be simpler to just use them locally in the translation unit and hide these details
- speeding up compilation - since the definitions of the dependencies are not included (speaking C++ here) and used by all the layers of the application
- speeding up possible refactorings - renaming a class which is passed as dependency through many layers would imply many modifications in many files as opposed to doing the changes only once in one place
Of course, when designing software there is no right solution for a problem - there is only the solution that solves the problem taking into consideration the context and the constraints.
Also, these “winner” solutions can be the correct ones for a given time only - since tech is changing so fast, yesterday’s solutions become obsolete and need to be updated - this is another reason why there are no perfect solutions.