Извините за кросс-посты по разработке программного обеспечения, не знал, что это не одобряется.
Ответ, который я получил, был именно тем, что я искал, для любопытных: https://softwareengineering.stackexchange.com/a/347143/269571
Оригинальный вопрос
Я читаю книгу «Гибкая разработка программного обеспечения, принципы, шаблоны и практики» от Роберт С. Мартин.
Когда он говорит о Принцип обращения зависимостей он приводит следующий пример нарушения DIP:
Это кажется мне очень ясным, как объект более высокого уровня Button
зависит от объекта более низкого уровня Lamp
,
Решение, которое он приходит с:
Он создает интерфейс таким образом, Button
больше не зависит от объекта Lamp
,
Теория кажется мне действительно ясной, однако я не могу обернуться, используя этот принцип в реальных проектах.
Кто собирается определить, какие классы (которые реализуют SwitchableDevice
) нужно называться?
Кто говорит Button
какие устройства ему нужно включать / выключать?
Как Вы говорите объект, который использует что-то Аннотация который бетон вещи, которые нужно использовать? (Пожалуйста, поправьте меня, если этот вопрос совершенно не верен)
Если что-то неясно по моему вопросу, пожалуйста, дайте мне знать, я буду рад разъяснить вам кое-что.
Весь смысл внедрения зависимости (по крайней мере, как я понял) заключается в том, что Button
не нужно знать, что конкретно SwitchableDevice
это переключение.
Абстрактный интерфейс может выглядеть так:
struct SwitchableDevice {
virtual void switchOn() = 0;
virtual void switchOff() = 0;
};
И кнопка может быть реализована так:
struct Button {
SwitchableDevice& dev;
bool state = false;
Button(SwitchableDevice& d) : dev(d) {}
void buttonPress(){
if (state) { dev.switchOff(); }
else { dev.switchOn(); }
state = !state;
}
};
Для кнопки, вот и все! Никто не должен сказать кнопке, какова конкретная реализация SwitchableDevice
Другими словами: реализация Button
и SwitchableDevice
развязаны
Возможная реализация Lamp
может выглядеть так:
struct Lamp : SwitchableDevice {
void switchOn(){std::cout << "shine bright" << std::endl;}
void switchOff(){std::cout << "i am not afraid of the dark" << std::endl;}
};
И это может быть использовано так:
int main(){
Lamp lamp;
Button button(lamp);
button.buttonPress();
button.buttonPress();
}
Надеюсь, это поможет…
Преимущество состоит в том, что теперь мы можем изменить реализацию Button
и Lamp
индивидуально, без необходимости что-либо менять с другой стороны. Например, ButtonForManyDevices
может выглядеть так:
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;
}
};
И так же вы можете изменить поведение Lamp
полностью (конечно, в пределах SwitchableDevice
без необходимости что-либо менять на кнопке. Такой же ButtonForManyDevices
может даже использоваться для переключения Lamp
, VaccumCleaner
и MicroWaveOven
,
Он говорит, что управление кнопками должно быть более обобщенным, чем просто лампа. Если у вас есть классы кнопок для каждого типа вещей, которыми кнопка может управлять, вы можете получить множество классов кнопок.
В первом примере описывается кнопка на лампе. По сути, он берет лампу за отправную точку и делит ее на составляющие.
Во втором примере он делит части и смотрит на кнопку более широко.
Кто будет определять, какие классы (реализующие SwitchableDevice) нужно вызывать?
Должна быть связь между кнопкой и интерфейсом.
Кто скажет Баттону, какие устройства ему нужно включать / выключать?
Класс Button должен был бы реализовать механизм, позволяющий определить, к какому устройству он подключен.
Как вы говорите объекту, который использует что-то абстрактное, какие конкретные вещи ему нужно использовать? (Пожалуйста, поправьте меня, если этот вопрос совершенно не верен).
Потому что объект, производный от абстрактного интерфейса, должен полностью реализовывать интерфейс. Объект Lamp должен где-то определять методы TurnOn и TurnOff.