Confused with SOLID and Dependecy Injection
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?
"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.
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.
You should distinguish DIP, IoC and DI:
DIP - dependency inversion principle - it only says what we should do and what shouldn't
IoC - inversion of control - a way to use DIP, it is basically a pattern to apply DIP.IoC describes different ways of inverting the control
DI - it's concrete implementation of IoC (like service locator, contextualized lookup, factory)
DIP, IoC, DI
the "high level class" will have to know about the MatchResult object which breaks the rules of DI.
No, this doesn't breaks DIP rules. You shouldn't know about specific implementation - in this case you should use interface (ie IMatcher) and create classes that implement this interface.
Then you can use any class that implements this interface:
public class Test
{
private IMatcher _matcher;
public Test(IMatcher matcher) // here you can pass any class than implements IMatcher, and you don't know specific implementation of this class - this is DI
{
this._matcher = matcher;
}
}
In your case MatchResult
is only result which is returned by some implementation.
If you want to return different MatchResult
s implementations, you can also use IMatchResult
interface instead of specific implementation to return implementation which you want.
上一篇: 依赖注入和服务定位器模式有什么区别?