So I am trying to learn the SOLID principles and Dependency Injection. I have read some blog posts on the subject and I am starting to understand a bit. However, there is one case that I can't find an answer for and will try to explain it here.
I have developed a library to do text matching, it contains a class Matcher
that has a function called Match
which returns the result in a MatchResult
object. This object contains information like percentage, time elapsed, whether it was a success or not and so on. Now from what i understand in dependency injection is that a high level class should not "know" about a low level class or module. So I have set up my library and the Matcher
class to use an interface for the Matcher
class, this will allow me to register it using an IoC container. However, because that function is returning a MatchResult
object, the "high level class" will have to know about the MatchResult
object which breaks the rules of DI.
How should I go about solving this problem and what is the recommended way to do so?
Dependency Injection is the method of providing the dependencies and Inversion of Control is the end result of Dependency Injection. IoC is a design principle where the control flow of the program is inverted. Dependency Injection is one of the subtypes of the IOC principle.
The Dependency Inversion Principle (DIP) states that high level modules should not depend on low level modules; both should depend on abstractions. Abstractions should not depend on details. Details should depend upon abstractions.
The main difference is how the dependencies are located, in Service Locator, client code request the dependencies, in DI Container we use a container to create all of objects and it injects dependency as constructor parameters (or properties). Dependency Injection doesn't require the use of a DI Container though.
In our example, CustomerBusinessLogic depends on the DataAccess class, so CustomerBusinessLogic is a high-level module and DataAccess is a low-level module. So, as per the first rule of DIP, CustomerBusinessLogic should not depend on the concrete DataAccess class, instead both classes should depend on abstraction.
DI states that the classes consuming an interface should not know any details about the concrete implementation. However, the MatchResult
is not an implementation detail, but part of the interface contract (a DTO, the return type of the Match
method) - and that's ok. You could have an additional class implementing that IMatcher interface in a different manner, but it should still return a MatchResult
, like it's expected of it.
"High level" and "low level" are terms associated with dependency inversion which has a relation to dependency injection but is a different concept. They both have the initials "DI", and the "D" in both stands for "dependency," so they can create some confusion.
(I think of it this way - dependency injection is a way of implementing dependency inversion.)
But in my opinion the terminology used when defining dependency inversion can be really confusing to .NET programmers trying to understand the concept. It's applicable, but some of the terminology isn't used among .NET developers.
From Robert Martin's definition, as quoted on Wikipedia,
High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.
What is a "high level module" and a "low level module?" If you find that confusing you're not alone. We don't really use those terms. The part that we can really understand and apply is that we should depend on abstractions.
In the case of MatchResult
, if it's just a container for a few properties then it's probably abstract enough. DTOs have been a common practice for some time, so if time had revealed that we needed to wrap them in interfaces then that pattern would have emerged by now. It doesn't hurt, but it's usually not necessary.
Going back to dependency inversion - the real confusion comes from the name itself. What is getting inverted? When you see diagrams like what's on the Wikipedia page, my recommendation is to look away from the blinding diagrams.
Martin explains his use of the word "inversion" this way (going back to his original paper on the subject)
One might question why I use the word “inversion”. Frankly, it is because more traditional software development methods, such as Structured Analysis and Design, tend to create software structures in which high level modules depend upon low level modules, and in which abstractions depend upon details. Indeed one of the goals of these methods is to define the subprogram hierarchy that describes how the high level modules make calls to the low level modules. Figure 1 is a good example of such a hierarchy. Thus, the dependency structure of a well designed object oriented program is “inverted” with respect to the dependency structure that normally results from traditional procedural methods.
In other words, the inversion is a contrast between applying dependency inversion, and the "traditional" style of not applying dependency inversion. That might be clearer if you're coming from a background in which 'high level modules depend upon low level modules,' (and you use the term "module.") But if that was not previously your 'tradition' then what are you 'inverting?' Nothing.
All of those details still have meaning, but they are extremely confusing when you're trying to learn these concepts for the first time. My suggestion is to apply this part, as you already are: Depend on abstractions.
If you do that then you're applying the principle because whatever "high-level modules" and "low-level modules" are, your classes won't be too dependent on other classes - high-level, low-level, or otherwise.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With