Reputation: 3060
Excuse me for cross-posting on Software Engineering, didn't know that it was frowned upon.
The answer I was got there was exactly what I was looking for, for those curious: https://softwareengineering.stackexchange.com/a/347143/269571
Original question
I'm reading the book "Agile Software Development, Principles, Patterns, and Practices" by Robert C. Martin.
When he talks about the Dependency Inversion Principle he gives the following example of a DIP violation:
This seems very clear to me, as the higher-level object Button
is depending on a lower-level object Lamp
.
The solution he comes with is:
He creates an interface so that way Button
is no longer depending on the object Lamp
.
The theory seems really clear to me, however I can't wrap my head around using the principle in real-life projects.
Who is going to determine what classes (that implement SwitchableDevice
) need to be called?
Who tells Button
what devices he need to turn on/off?
How do you tell an object that uses something abstract which concrete things it needs to use? (Please correct me if this question is completely wrong)
If anything is unclear about my question, please let me know, I'll be glad to clarify things for you.
Upvotes: 4
Views: 1585
Reputation: 123114
The whole point of dependency injection (at least as I understood it) is that the Button
does not need to know what concrete SwitchableDevice
it is switching.
The abstract interface could look like this:
struct SwitchableDevice {
virtual void switchOn() = 0;
virtual void switchOff() = 0;
};
And the button could be implemented like this:
struct Button {
SwitchableDevice& dev;
bool state = false;
Button(SwitchableDevice& d) : dev(d) {}
void buttonPress(){
if (state) { dev.switchOff(); }
else { dev.switchOn(); }
state = !state;
}
};
For the button, thats it! Noone needs to tell the button what is the concrete implementation of the SwitchableDevice
, in other words: The implementation of the Button
and the SwitchableDevice
are decoupled.
A possible implementation of a Lamp
could look like this:
struct Lamp : SwitchableDevice {
void switchOn(){std::cout << "shine bright" << std::endl;}
void switchOff(){std::cout << "i am not afraid of the dark" << std::endl;}
};
And that could be used like this:
int main(){
Lamp lamp;
Button button(lamp);
button.buttonPress();
button.buttonPress();
}
Hope that helps...
The benifit is that now we can change the implementation of the Button
and the Lamp
individually, without having to change anything on the other part. For example a ButtonForManyDevices
could look like this:
struct ButtonForManyDevices {
std::vector<SwitchableDevice*> devs;
bool state = false;
Button(std::vector<SwitchableDevice*> d) : devs(d) {}
void buttonPress(){
if (state) for (auto d: devs) { d.switchOff(); }
else for (auto d: devs) { d.switchOn(); }
state = !state;
}
};
And similarly you could change the behaviour of the Lamp
completely (of course within the limits of SwitchableDevice
without having to change anything on the button. The same ButtonForManyDevices
could even be used to switch a Lamp
, a VaccumCleaner
and a MicroWaveOven
.
Upvotes: 3
Reputation: 21647
He's saying that what a button controls should be more generalized than just a lamp. If you have button classes for each type of thing that a button can control, then you could wind up with a lot of button classes.
In the first example, one is describing a button on a lamp. It essentially is taking the lamp as the starting point and dividing it into components.
In the second example, he is dividing the parts and looking at a button more generally.
Who is going to determine what classes (that implement SwitchableDevice) need to be called?
There is going to have to be a link between the button and the interface.
Who tells Button what devices he need to turn on/off?
The Button class would need to implement a mechanism to tell what device it is connected to.
How do you tell an object that uses something abstract which concrete things it needs to use? (Please correct me if this question is completely wrong).
Because an object that derives from an abstract interface must fully implement the interface. A Lamp object must define a TurnOn and TurnOff method somewhere..
Upvotes: 0