4

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:

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:

Solution

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.

BDL
  • 21,052
  • 22
  • 49
  • 55
Melvin Koopmans
  • 2,994
  • 2
  • 25
  • 33
  • @tobi303 `Button` uses an abstract class instead of something concrete, meaning that `Button` is no longer depending on `Lamp` (or anything concrete). In the first example, `Button` required `Lamp`, meaning that a high-level object is depending on a lower-level object. Instead of the inverse: a low-level object depending on a high-level object. Which is exactly what you want with DIP. As far as I have read the book, I haven't seen any examples of **how** you would tell the button which concrete implementations it has to use – Melvin Koopmans Apr 13 '17 at 17:50
  • 1
    give me a second I will write an answer. already too much blabla in comments ;) – 463035818_is_not_an_ai Apr 13 '17 at 17:52
  • That's my question: who tells `Button` what concrete implementation of `SwitchableDevice` it should use? – Melvin Koopmans Apr 13 '17 at 17:53
  • This may be better asked on [softwareengineering.se]. – Thomas Matthews Apr 13 '17 at 17:55
  • I will delete my comments, as the answer contains all I said plus an exmaple.. – 463035818_is_not_an_ai Apr 13 '17 at 18:02
  • @ThomasMatthews when referring other sites, it is often helpful to point that [cross-posting is frowned upon](https://meta.stackexchange.com/tags/cross-posting/info) – gnat Apr 13 '17 at 20:37
  • @gnat see my edit :) – Melvin Koopmans Apr 13 '17 at 20:50

2 Answers2

3

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.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Mmh, yeah, this makes things a lot clearer. So you could also inject an array with `SwitchableDevice` objects, iterate over that array and call the method? – Melvin Koopmans Apr 13 '17 at 18:06
  • @MelvinKoopmans That would be a different button. However, note that you could change the above implementation of `Button` to take an array of `SwitchableDevices` to switch an array of devices and you would not have to change a single line of the implementation of `Lamp` or the interface `SwitchableDevice` – 463035818_is_not_an_ai Apr 13 '17 at 18:08
  • 2
    @MelvinKoopmans To follow up on that example, I think a better one would be that a button still controls **one** `SwitchableDevice`. However, there can be a `CompositeSwitchableDevice` (i.e. a coord that splits) that still implements `SwitchableDevice` but in *its* constructor takes a list of `SwitcahbleDevice`s. Now button doesn't need to change and neither does lamp. You just did dependency injection. – default Apr 13 '17 at 18:12
  • 1
    "noone needs to tell" - not directly. But at some point somewhere in the code a construction graph needs to be invoked (i.e. to pass an instance of Lamp to the instance of Button). – Oliver Charlesworth Apr 13 '17 at 18:14
  • @OliverCharlesworth yeah sure. It wasnt meant too literally. What I wanted to say is that even if the `Button` needs to get a concrete instance passed, it does not need to know of what type it is (as long as it implements the interface) – 463035818_is_not_an_ai Apr 13 '17 at 18:16
  • @Default thats of course nicer than what I just added. If you want, feel free to edit the answer and replace my not so nice `ButtonForMany` with something nicer – 463035818_is_not_an_ai Apr 13 '17 at 18:18
  • @tobi303 unfortunately I'm a bit of a rookie on DI for C++. I know the principles of DI and what has worked for me, since I've used it extensively in C# but those are two completely different languages. – default Apr 13 '17 at 18:25
  • @Default until 1 hour ago I didnt know that this is called dependency injection. For me its just called "if you dont know what will be the concrete implementation or you want to be flexible for future changes use an abstract interface" ;) – 463035818_is_not_an_ai Apr 13 '17 at 18:47
0

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..

user3344003
  • 20,574
  • 3
  • 26
  • 62
  • So some other object would insert, lets say an array of objects, into the constructor of `Button`. Then `Button` iterates over the objects and calls the methods. – Melvin Koopmans Apr 13 '17 at 18:01
  • 1
    I don't agree that `Button` needs any mechanism to determine the device it's connected to. The whole reason of DI is that it shouldn't need to know. – default Apr 13 '17 at 18:02
  • A button could have a member that is pointer to to an object implementing the SwitchableInterface . Probably a button would control a single object. – user3344003 Apr 13 '17 at 18:04
  • 1
    It doesn't need to know the TYPE of object it is connected to (other than that it implements SwitchableInterface) but its got to know that it is contected to somethng. – user3344003 Apr 13 '17 at 18:05
  • "The Button class would need to implement a mechanism to tell what type of device it is connected to. " Why?? – 463035818_is_not_an_ai Apr 13 '17 at 18:05
  • My misteak in typing. It needs to know the DEVICE it is connected to but not its TYPE. – user3344003 Apr 13 '17 at 18:08
  • The question is: who injects the lamp into the button, not how button uses the object per se. Who's responsibility is it to tell button that it should control the lamp? – default Apr 13 '17 at 18:08
  • That's another design decision that illustrates where this kind of analysis can go down a theoretical rathole. You could create a Lamp object that Has a button (with the drawback that Lamp is not properly abstracted). You could have Lamp Objects with controls where you add a button to the list of controls. That creates implementation problems as well. This analysis probably leads you to needing a Model 42 Lamp Class that HAS A button. – user3344003 Apr 13 '17 at 18:13
  • what? no. You don't need that. Dependency Injection handles it via the abstraction. How you arrived at a Lamp needing a Button is beyond me. But *The Button class would need to implement a mechanism to tell what device it is connected to* is still not (IMO) a valid answer to the question *Who tells Button what devices he need to turn on/off?* – default Apr 13 '17 at 18:22
  • That is exactly what I am saying, A lamp might not need a button. In order to reach the point where a lamp would have a button you would need to have a specific lamp model that has a button. E.g. a Model 42 Lamp Class that is derived from Lamp that has a Button member and this class tells the button that it is the one that needs to be turned off. – user3344003 Apr 13 '17 at 18:32