The definition of the principle, from Robert Martin’s 1996 paper The Dependency Inversion Principle is this:
A. HIGH LEVEL MODULES SHOULD NOT DEPEND UPON LOW LEVEL MODULES. BOTH SHOULD DEPEND UPON ABSTRACTIONS.
B. ABSTRACTIONS SHOULD NOT DEPEND UPON DETAILS. DETAILS SHOULD DEPEND UPON ABSTRACTIONS.
What are high level modules? What are low level modules? What is a dependency and how do I invert one? Some of these questions will be answered.
Dependency Inversion is not a language-specific concept and obviously predates the .NET Framework. This explanation, however, is .NET-centric.
What Is a Dependency?
A dependency is something - another class, or a value - that your class depends on, or requires. For example:
- If a class requires some setting, it might call
ConfigurationManager.AppSettings["someSetting"]
.ConfigurationManager
is a dependency. The class has a dependency onConfigurationManager
, which is the same thing in more words and the passive voice. But people say that. - If you write two classes - let’s say
Log
andSqlLogWriter
- andLog
creates or requires an instance ofSqlLogWriter
, thenLog
depends onSqlLogWriter
.SqlLogWriter
is a dependency.
Did you know there was a word for that? For a while I didn’t. And without that word, I didn’t realize that those two scenarios have something in common, that both describe classes with dependencies. It’s eye-opening for me to reflect on how just acquiring a word influenced my perception and thinking.
Until I saw that fundamental commonality - I write classes that depend on other classes - there was no way to grasp the common problems stemming from how I depended on other classes. I saw the results: My code was difficult to maintain. Unit testing was foreign because my dependencies made my code untestable, so I relied primarily on unreliable manual testing. The term dependency revealed the commonality, and the Dependency Inversion principle revealed a better way to manage dependencies.
What Are High Level Modules and Low Level Modules?
The first part of the definition mentions “high level modules” and “low level modules.” What are “modules?” It’s just a difference in terminology between C++ and C#. In C# we write classes. C++ also uses classes, but that’s beside the point. Forget modules and think about classes.
The principle says that “both should depend on abstractions.” That means that when it comes to applying the principle, the difference between high level and low level doesn’t matter. Those terms mean something, but we can apply the principle without attempting to classify our classes as either high or low level. If you want to know more about those terms, Google them or read Robert Martin’s paper. But for now don’t worry about them. I don’t mean to trivialize it, but you can assign a lower priority to understanding those details.
What Are Abstractions?
The principle states that we should “depend on abstractions.” Abstractions are generally interfaces, abstract classes, and delegates (in .NET terms.) We call it an “abstraction” because it’s an indirect representation of a concrete class or method. Interfaces are used most commonly. So for the sake of discussion, applying dependency inversion means that we depend on interfaces. (This article clarifies that interfaces are not abstractions, but abstractions are usually interfaces or sometimes base classes. I mention it to acknoweldge that I’m generalizing. Pretend that interface and abstraction are synonymous even though they’re not.)
3/22/2018 - This post discusses the use of functions or delegates as abstractions.
How Do We Apply Dependency Inversion?
Take the example of a class that depends on AppSettings
:
public class ClassThatDependsOnSettings
{
public void DoSomethingThatNeedsSettings()
{
var someSetting = ConfigurationManager.AppSettings["someSetting"];
var otherSetting = ConfigurationManager.AppSettings["otherSetting"];
CallSomeOtherMethod(someSetting, otherSetting);
}
}
This class depends on ConfigurationManager
. There must be a web.config or app.config or this class can’t work. Here’s what the same class might look like if it depends on abstraction - in other words, it depends on an interface instead of depending on ConfigurationManager
.
First, the interface:
public interface ISettings
{
string SomeSetting { get; }
int OtherSetting { get; }
}
Then the class:
public class ClassThatDependsOnSettings
{
private readonly ISettings _settings;
public ClassThatDependsOnSettings(ISettings settings)
{
_settings = settings;
}
public void DoSomethingThatNeedsSettings()
{
CallSomeOtherMethod(_settings.SomeSetting, _settings.OtherSetting);
}
}
Our initial requirements haven’t changed - we still want to get our values from AppSettings
, so we write an implementation of ISettings
that does just that:
public class AppSettings : ISettings
{
public string SomeSetting { get { return ConfigurationManager.AppSettings["someSetting"]; } }
public int OtherSetting { get { return int.Parse(ConfigurationManager.AppSettings["otherSetting"]); } }
}
This second version applies the principle of Dependency Inversion because it depends on an abstraction - the ISettings
interface. It’s a humble example and just a beginning.
Why Does It Matter?
Now we can easily replace one implementation of ISettings
with another. Our class doesn’t know or care because as far as it is concerned, the implementation of the interface doesn’t matter.
Sooner or later we’re going to find that we want a class to use one dependency in one scenario and a different one in another scenario. Perhaps in some cases we want those settings to come from user input instead of AppSettings
. So we might do something funky we regret later like putting weird arguments in our constructor or other methods like useAppSettings
and branching within the code, or we create a duplicate version of the same class, or we try to solve the problem with inheritance. I’ve done all three and more.
Some might call YAGNI on that. We’re planning for extensibility that we will likely never need. But if we intend to write unit tests for our classes then we already require that flexibility - sooner is now. We need to be able to test a class in isolation, not testing all of its dependencies at the same time. The example above is simple because the class only has one dependency. But what if our class has a few dependencies, and those have dependencies, and so on? If we haven’t applied Dependency Inversion then it’s impossible to test one class without also testing every class it depends on. That’s not really unit testing, and it defeats one of the benefits of unit testing, which is finding bugs quickly by testing small units of code. If we haven’t applied this principle then we likely haven’t written unit tests, and we may never have experienced the awesomeness of effortlessly finding and fixing a bug in a method that we wrote three minutes ago as opposed to building the whole car, turning the key, and debugging the whole thing from end to end. The time taken to write unit tests quickly pays for itself.
Even in the simple example above, if we depend on AppSettings
, how do we write unit tests to test our class with different settings? It’s impossible because we can’t have more than one version of an AppSettings
key (unless we contort our code further to account for that.) But if we depend on an abstraction (interface) then we can use an implementation of our interface created just for testing purposes. For example:
public class TestScenarioOneSettings : ISettings
{
public string SomeSetting { get { return "A"; } }
public int OtherSetting { get { return 3; } }
}
There are different ways to create these “fake” dependencies. We can write simple classes like the one above, sometimes called test doubles. Tools like Moq provide easy ways to create implementations of interfaces with methods and properties that return predetermined results. That multiple versions of such frameworks are created and maintained for various languages underscores that many developers value unit testing with test doubles or mocks.
Finally, Dependency Inversion is the D in SOLID, a set of established, tested principles that improve the quality and maintainability of our code. If this is the only one of those principles we apply we’ll still get some benefit. But applying one leads to applying the others. We see how much one helped so we examine another more closely. It also happens because these principles complement each other. For example, the Interface Segregation principle improves how we design the interfaces on which other classes depend. The Liskov Substitution principle ensures that our class can depend on an interface without “knowing” what the implementation is.
What Gets Inverted?
You may find it useful or interesting trying to visualize what is getting inverted. But that’s just the name of the principle. The principle tells us to depend on abstractions, and that’s how we apply it. It doesn’t tell us to invert anything.
In his paper, immediately after defining the Dependency Inversion principle, Robert Martin states,
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.
He was recommending that developers look at the way they traditionally structured their software and managed their dependencies, and invert it. To me this makes the name of the principle (not the principle itself) self-referential. Dependency Inversion is an inversion of not applying Dependency Inversion. (He’s Robert Martin, he wrote the awesome paper, and he gets to call it whatever the heck he wants whyever he wants. Plus if he called it something else it might not fit the SOLID acronym.)
Other Resources
It’s my hope that you can Google “Dependency Inversion” and that, as a result of reading this, you’ll find more in-depth articles more accessible. You’ll also see how oversimplified my examples are.
Some topics that follow Dependency Inversion are Dependency Injection and Dependency Injection containers, also called IoC (Inversion of Control) containers. It’s not the same as Dependency Inversion, but a way of implementing and managing it. (Both have the initials “DI” which breeds confusion.) My experience is that it’s difficult to learn the how and the why simultaneously. I recommend taking a leap of faith and starting with how to use an IoC container, and then the why will make sense faster.
Dependency Injection is even built into ASP.NET Core. Prior to that if you wanted to use an IoC container in a web application you’d have to add a 3rd-party library like Castle Windsor, Unity, or others. Clearly many developers consider it an essential pattern.
I also recommend reading all of the posts in Robert Martin’s blog. This blog inspires me and makes we want to be a better developer. I haven’t yet adopted TDD (Test-Driven Development) as he advocates but I’m certain that I’m going to try it with serious intent.
Diagrams?
This is one case where I’ll assert that diagrams don’t help. If someone’s mind is working to comprehend a new principle and unfamiliar terminology at once, a diagram may be just the straw that congnitively overloads the camel. Not only can they not mentally process the diagram, they may become less able to process what they’ve read than if they hadn’t seen the diagram. Maybe that’s just me. Maybe I’m lazy and I should try harder to follow the diagrams. Google “Dependency Inversion”, check out the diagrams found alongside the explanations, and judge for yourself.
Top comments (0)