I'm working with dependency injection at the moment. Effectively this involves the UI layer (e.g. a web application) including a DI container which has a whole bunch of data about interfaces it will work with, and which implementation to use for each.
However, because of the need to satisfy dependencies, the DI container also needs to know about interfaces and classes it would not otherwise need access to. For example:
The UI layer needs to work with an IWidgetManager interface. The configured implentation is ConcreteWidgetManager. This is registered in the DI container.
The ConcreteWidgetManager has a dependency on IWidgetRepository. The desired implementation for this is ConcreteWidgetRepository. Thus the DI container must also know about IWidgetRepository and ConcreteWidgetRepository.
Had I simply hardcoded ConcreteWidgetManager's dependency on ConcreteWidgetRepository, then ConcreteWidgetRepository could be made internal and therefore invisible to my UI layer. No UI code could bypass the manager layer and work directly with the repository layer.
Therefore it seems that the use of DI, although an awesome pattern in many respects, defeats encapsulation from the point of view of the UI layer.
Am I right in thinking this, and is there any way to mitigate the situation?
3 Answers 3
No, Dependency Injection is not breaking encapsulation. It is the way you layered your application that is breaking encapsulation. By separating the interfaces and implementations in different assemblies and putting the container configuration in its own assembly, you can prevent the UI layer from having to take a dependency on the concrete ConcreteWidgetManager, or even the IWidgetRepository if you wish.
However, adding extra assemblies has cost. Both in maintenance and in time it takes to compile your solution. Instead of enforcing architectural rules using assemblies, I would enforce them with clear and simple guidelines, or perhaps with a tool such as NDepend. However, when the team is small enough, using code reviews will be effective enough to prevent architectural erosion.
3 Comments
The way you're describing this implies that the UI layer is responsible for creating the DI container.
This might be true in your particular application. But remember that the initialization part is a tiny part of the overall code.
Yes, there is SOME code that creates the DI container and the UI layer in some particular order. It might be the case that the UI layer invokes a function CreateDIContainer which initializes all of the components. But this one function is the only instance where the implementations are mentioned; all other aspects of the UI layer deals with the abstract interfaces. A purist might take issue, but really, in the 99.5 percent of the code that ISN'T CreateDIContainer the UI does not know what the implementations are. Move the initialisation function into a separate static class in a separate library if that makes you happier.
I'm also intruiged about you saying
Had I simply hardcoded ConcreteWidgetManager's dependency on ConcreteWidgetRepository ....,
That you brought it up as an option makes me wonder: do you have any reason NOT to hardcode this relationship? I use dependency injection when I have to - the end benefit is that I can mock parts of the code away in my automated tests.
Are you going to want to mock out the ConcreteWidgetRepository? If you are not, then go ahead and hardcode the relationship. Otherwise you're just introducing architecture for the sake of architecture.
(See You Aren't Gonna Need It.)
5 Comments
Yes, It does break encapsulation. But that's the price you need to pay for using DI. It makes your code more testable but yes breaks encapsulation.
From an API perspective of a class, your class that asks for dependencies to be injected becomes as dumb as possible but the class that calls this class now knows too much. In your case the framework. If you use DI in general in your code in terms of say, constructor injection, you calling class needs to know about several classes.
Comments
Explore related questions
See similar questions with these tags.
ConcreteWidgetRepositoryvisible to the UI layer? The fact that it's mentioned in the framework's configuration doesn't mean that it's "visible," unless your UI code has an auto-wired reference toIWidgetRepository. And that's a coding problem, one that's exactly equivalent to your UI code containing a hard-coded reference toConcreteWidgetRepositoryConcreteWidgetRepository(which it does, to inject one intoConcreteWidgetManager), than that knowledge is also freely available elsewhere in the UI layer.