3

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?

David
  • 15,750
  • 22
  • 90
  • 150
  • 2
    The answer is yes, DI does break encapsulation. It's a duplicate of http://stackoverflow.com/questions/1005473/must-dependency-injection-come-at-the-expense-of-encapsulation, See also http://www.bryancook.net/2011/08/on-dependency-injection-and-violating.html – Amittai Shapira Sep 14 '11 at 11:29
  • How is `ConcreteWidgetRepository` visible 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 to `IWidgetRepository`. And that's a coding problem, one that's exactly equivalent to your UI code containing a hard-coded reference to `ConcreteWidgetRepository` – parsifal Sep 14 '11 at 11:30
  • @parsifal - the DI container is part of the UI layer. If it needs to know about the `ConcreteWidgetRepository` (which it does, to inject one into `ConcreteWidgetManager`), than that knowledge is also freely available elsewhere in the UI layer. – David Sep 14 '11 at 12:52
  • @David - in my view, the DI container *is* the application: it associates the UI with the business logic, and wires any internal dependencies, but it is not *part* of either. – parsifal Sep 14 '11 at 13:13
  • @parsifal - I don't know what platform you develop on so I'm not quite sure how to frame the question, so I'm going to give it to you .NET style. How do you create your solution then? The DI container is in a different project to the UI project (e.g. web application)? – David Sep 14 '11 at 14:13
  • @David - fair question. I was thinking in terms of Spring MVC, which has a clear separation between controller and view. The objects that implement those components could come from the same project, or from different (dependency) projects. But without the wiring provided by the beans.xml file, those independent components would have no knowledge of each other. Thus my statement that the config file (ie, framework) "is the application." – parsifal Sep 14 '11 at 16:01
  • @parsifal - I have learnt that the solution to this problem is to place the DI resolving logic in a different assembly to the UI layer. Then, the DI assembly can know about all the implementations and the UI assembly doesn't have to. – David Oct 13 '11 at 08:51

3 Answers3

2

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.)

Andrew Shepherd
  • 44,254
  • 30
  • 139
  • 205
  • the DI container does only comprise a small fraction of the UI layer code, but the fact it needs to know about these classes means that the rest of the UI layer does too. – David Sep 14 '11 at 12:54
  • The point you make about whether or not to hardcode a dependency is an interesting one. The question is how likely am I to want another implementation? Occasionally there are 'operational' reasons why I meet need another implementation (e.g. change of provider) but more often it's for unit testing purposes. My instinct is to use an interface and DI for any service class unless I'm pretty sure I won't need another implementation. – David Sep 14 '11 at 12:57
  • By 'service' class I mean anything that exposes behaviour rather than simply state. – David Sep 14 '11 at 12:57
  • 1
    @David - About whether or not to hardcode a dependency - here's where I follow the YAGNI principal. (http://c2.com/cgi/wiki?YouArentGonnaNeedIt) There's no reason why you can't make it an injected dependency later on when you know that will solve a problem. – Andrew Shepherd Sep 14 '11 at 22:51
  • Cool, thanks for your thoughts. This question has really made me think about DI again. – David Sep 19 '11 at 08:36
2

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.

Steven
  • 166,672
  • 24
  • 332
  • 435
  • There's a lot of interesting stuff to think about here. Thank you. – David Sep 19 '11 at 08:38
  • I would like to spend some time setting up a quick solution and seeing if I can get this approach to work. If it does work, I'll give you the points! – David Sep 19 '11 at 08:47
  • Okay, I've experimented with your suggestion about placing interfaces and implementations in different assemblies and it works! The UI assembly needs a reference to the interface assembly, and to a 'wiring' assembly that does the DI resolving. The wiring assembly has a reference to both the interface assembly and the implementation assembly. Thus, the UI assembly has no direct reference to the implementation assembly, but still gets the implementations injected in! This is absolutely the answer to my question. – David Oct 13 '11 at 08:48
1

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.

Ravi Bhatt
  • 3,147
  • 19
  • 21