I'm reading about design patterns. I know what this principle does.
High-level and low-level classes depend on abstractions. But why we say this is inversion?
I'm reading about design patterns. I know what this principle does.
High-level and low-level classes depend on abstractions. But why we say this is inversion?
In traditional programming, the flow of the business logic is determined by objects that are statically assigned to one another. With inversion of control, the flow depends on the object graph that is instantiated by the assembler and is made possible by object interactions being defined through abstractions. The binding process is achieved through dependency injection, although some argue that the use of a service locator also provides inversion of control.
Or in other words: in traditional, non-inverted control higher-level components depend on lower-level ones.
This has been demonstrated as a limitation, because high-level components lose the chance of working with more than an environment or low-level excessively specific components.
With inversion of control, the above paradigm is inverted. The high-level components are just abstract classes or interfaces and these are usually (and should be) declared in a package or assembly having no dependency on the code implementing them, and it's the implementation the code that's dependent on higher-level code (because the low-level code must implement a high-level interface or abstract class).
This is the inversion!
Quote of Bob Martin in his original article on the topic:
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 depen- dency structure of a well designed object oriented program is “inverted” with respect to the dependency structure that normally results from traditional procedural methods.
So as I understand, having the inversion priciple in place makes the high level policy described in terms of abstraction rather than concrete utility components. And from the point of view of the high level module the abstract policy remains stable. Any changes of the low level implementations do not force the high level module to change.
The term 'inversion' distinguishes the approach from the traditional model.
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.
I'll put a direct example of the inversion of control.
Imagine you have a RemoteControl
class:
class RemoteControl {
private TV television;
public RemoteControl() {
this.television = new TV();
}
function turnOn() {
this.television.turnOn();
}
}
As you can see RemoteControl is tightly coupled with the Television class, a low-level component or a concrete utility component. That means Television depends on the button class to be turned on.
Inversion of control dictates that RemoteControl must not depend on concrete utility components or low-level components such as the Television class. We must find a way to abstract the RemoteControl and Television concrete implementations into a "high-level" ideas in order for the design to be decoupled.
If we do that, we could make the television a concept that can be turned on with multiple interchangeable low-level utility components (switch, remote control, gestures, etc), and the same for the remote control. It can be a component that can turn on different interchangeable low-level utility components (radio, AC, lights, etc)
The solution would be something like this:
interface OnOffAbleComponent {
public function turnOn();
public function turnOff();
}
class Television implements OnOffAbleComponent {
function turnOn() {
...
}
function turnOff() {
...
}
}
interface Switch {
public function turnOn();
public function turnOff();
}
class RemoteControl implements Switch {
function turnOn() {
this.onOffAbleComponent.turnOn();
}
function turnOff() {
this.onOffAbleComponent.turnOff();
}
}
Now Television doesn't know if it's a "button", "remote control", or a "human gesture" the thing that is turning the television on. Television only knows about some "abstract idea" that allows it to be turned on. Our old component "RemoteControl" now implements an interface SomethingThatTurnsOnTVInterface, which is a high-level component or "the high-level abstract idea".
So we went from concrete ideas to abstract ideas:
lowLevel-----------------------lowLevel
RemoteControl ---- depends on ----> Television
To:
highlevel -------------------------highLevel
Switch -----depends on --------> OnOffAbleComponent
Television is not coupled to RemoteControl anymore.
We have successfully inverted the control from low-level to high-level.
turnOn()
read like this: function turnOn() { this->thisTurnsOnTV->turnOnTV(this); }
and anything that can turn on something can turn on anything that is able to be turned on. But in your example how is the object that turns on the Television able to interact with the Television if it has no reference to it?
Commented
Dec 26, 2013 at 20:49
The problem is that so-called "Dependency Inversion Principle" is poorly named and defined. While the definition was clarified by its author afterwards, the name is a total misnomer. There are no dependencies which are inverted when this principle is applied.
Traditionally, your dependencies would descend downwards in your application.
That is, you would have a high level class A, which depends on a lower level class B.
Consider a car. In traditional programming, your car object would depend on your steering wheel object, which would depend on your two front wheel objects. This means that if your wheels change, your steering wheel class needs to change, and your car class might even need to change.
Car -> SteeringWheel -> Wheels
Dependency inversion gets rid of these dependencies and adds two new dependencies:
Car -> ISteeringWheel
SteeringWheel implements ISteeringWheel
SteeringWheel -> IWheels
Wheels implements IWheels
This change means that we can now test the car independently from the SteeringWheel, the SteeringWheel independently from the wheels. We can also change the implementation of the Wheels or the SteeringWheel without needing to make changes to one of the parent classes.
This makes for a much more flexible architecture and helps us ensure that the other SOLID principles (particularly the SRP) are not violated.
The inversion is not a reversing of the dependencies (after all, the Wheels are not dependent on the SteeringWheel now) but is turning the dependencies 90 degrees to a new abstraction. Inversion is possibly a poor choice of word. A more descriptive choice might be "Dependency Right Angled Turn". But that's not as catchy.