(upbeat music)
- [Eric] The dependency and version principle
gives us guidance to not design our high-level modules
to depend on our low-level modules,
but that sounds counterintuitive,
because it advises us to think about designs
in a manner that's really the opposite
of what we're used to.
We're used to thinking about designs
as top-down decompositions.
We take a problem and we factor it
into a high-level set of components
that depend on a lower level set of components.
We often think of the high-level components
as some kind of policymakers
that are commanding a set of low-level components
who are really carrying out all the real work.
The problem with this approach
is that it typically tightly couples
our high-level components to our low-level ones.
A symptom of this tight coupling
is that it's often difficult to take
that high-level component and then to go reuse it
with the totally different underlying implementation
of the low-level components.
If our design is right,
that's something we really should be able to do.
The dependency inversion principle
tells us to think of this in a different way,
by reversing the direction of this dependency.
It tells us really two things.
First, as we've said, high-level modules
should not depend on low-level modules,
actually both should depend on abstractions.
And second, the principal offers that abstractions
should not depend on details,
actually details should depend on abstractions.
Both of these statements require some explanation.
Let's take a look at an example.
Consider this remote control class.
It has a click method that's called one user indicates
they want to control a television.
The remote control depends
on a lower level class call television,
and that class offers two methods,
turn TV on and turn TV off.
So when the user clicks,
the remote control click method has the logic to toggle
between the on and off states by calling the turn TV on
and turn TV off methods respectively.
So this design looks straightforward enough,
but there is room for improvement.
For starters, this remote control controls only televisions
and more specifically the television represented
by the concrete television class.
What if we want to control another kind of television
or even another kind of device with an on-off switch?
It's not hard to notice that we have a high level component,
our remote control depending directly
on a low-level component,
the concrete television class.
So according to our principal, that can't be good.
But how else would we approach this?
Let's think through what the high level policy
for this design should be.
What's the high level policy?
Well as Robert Martin,
the originator of this principle says,
"It's the abstraction that underlies the application."
So, how's this for an improved high-level policy
or abstraction, or remote control
that can control anything with an on off button,
a TV, a light, a sprinkler system, and so on.
Let's express that in our class diagram.
To do that,
we're going to create an abstraction for an on-off device.
More specifically, we're going to create an interface
on off device that supports two methods,
turn on and turn off.
Next, we'll have our remote control
depend on that abstraction by composing our remote control
with an on-off device.
And remember before the remote control dependent
on a concrete type, that television class.
So, now a remote control no longer depends
on a low level class.
Instead, it actually depends on an abstraction
that's that on off device interface.
This goes the other way too
remember that our principal also tells us
that abstractions should not depend on details,
rather details should depend on abstractions.
Well, let's examine the television class now
and notice first that it implements
the on-off device interface,
no longer does it expose its proprietary turn TV on
and turn TV off methods.
So rather than our abstraction, the on-off device,
depending on the low level details of the television,
that is it's turned TV on and turn TV off methods,
it is the television that is depending
on the high level of abstraction,
that is the turn on and turn off methods
in the on off device interface.
So this is often the reverse of how we think of design,
but by adhering to the dependency and version principle,
we really free our high-level components
from being dependent on the details of low-level components.
This really helps us avoid rigidity,
fragility and immobility.
The dependency in version principle
tells us that all relationships between components
shouldn't involve abstract classes and interfaces,
not concrete classes, in doing so helps us design software
that's reusable and resilient to change,
because these abstractions allow the details
between the components to remain isolated from each other.