Принцип «Инверсия зависимостей» на современном C++

Добавлено 17 февраля 2022 в 21:10

Инверсия зависимостей – еще один принцип из SOLID, который поможет нам улучшить абстракцию. Применяя этот подход, мы отделяем определение интерфейса от фактической реализации. Чтобы объяснить этот паттерн, приведем классический пример с лампочкой и выключателем. Если предположим, что мы хотим разработать реализацию, ориентированную на классы, нам нужно разработать класс для лампочки и класс для выключателя.

Паттерн «Инверсия зависимостей» на современном C++
#include <iostream>

class LightBulb 
{
 public:
  void TurnOn() 
  {
    std::cout << "Light bulb on..." << std::endl;
  }

  void TurnOff() 
  {
    std::cout << "Light bulb off..." << std::endl;
  }

};

и

#include "light_bulb_plain.h"

class ElectricPowerSwitch 
{
 public:
  ElectricPowerSwitch(LightBulb light_bulb) : light_bulb_{light_bulb}, on_{ false} {}

  void press() 
  {
    if (on_) 
    {
      light_bulb_.TurnOff();
      on_ = false;
    }
    else 
    {
      light_bulb_.TurnOn();
      on_ = true;
    }
  }

 private:
  LightBulb light_bulb_;
  bool on_;
};

Когда дело доходит до использования, это будет выглядеть так:

#include "electric_power_switch_plain.h"

int main() 
{
  LightBulb light_bulb{};
  ElectricPowerSwitch power_switch{light_bulb};
  power_switch.press();
  power_switch.press();

  return 0;
}

Всё работает, но это то, что нам нужно? Нет, явно нет. Выключатель сильно зависит от лампочки; с точки зрения непрофессионала, этот выключатель может работать только с конкретной лампочкой. Внутри выключателя мы вызываем методы включения и выключения лампочки. Что, если бы выключатель не зависел от лампочки? Что, если бы выключатель мог включать и выключать всё, что можно включить/выключить: лампочку, вентилятор, кофемашину? А для этого нам нужно сначала определить абстрактный класс включаемого/выключаемого устройства или, лучше, интерфейс. В этом интерфейсе мы указываем так называемые связи, которых должно придерживаться каждое выключаемое устройство. У него есть два метода: включение и выключение (плюс виртуальный деструктор).

class Switchable 
{
 public:
  virtual void TurnOn() = 0;
  virtual void TurnOff() = 0;
  virtual ~Switchable() = default;
};

Наш выключатель должен зависеть только от него. Как же изменить класс выключателя? Легко.

#include "switchable.h"

class ElectricPowerSwitch 
{
 public:
  ElectricPowerSwitch(Switchable &switchable) : switchable_{switchable}, on_{false} {}
  void press() 
  {
    if (on_) 
    {
      switchable_.TurnOff();
      on_ = false;
    }
    else 
    {
      switchable_.TurnOn();
      on_ = true;
    }
  }
 private:
  bool on_;
  Switchable &switchable_;
};

Теперь нам нужно сделать лампочку подклассом интерфейса Switchable.

#include "switchable.h"
#include <iostream>

class LightBulb final : public Switchable 
{
  void TurnOn() override 
  {
    std::cout << "Turn on light bulb" << std::endl;
  }

  void TurnOff() override 
  {
    std::cout << "Turn off light bulb" << std::endl;
  }

};

Теперь, когда дело доходит до использования, это будет выглядеть так:

#include "electric_power_switch.h"
#include "light_bulb.h"
#include "fan.h"


int main() 
{
  LightBulb light_bulb{};
  Fan fan{};

  ElectricPowerSwitch switch1{light_bulb};
  switch1.press();
  switch1.press();

  ElectricPowerSwitch switch2{fan};
  switch2.press();
  switch2.press();

  return 0;
}

И вы можете увидеть, что есть также вентилятор, который можно включать, и у нашего класса выключателя нет никаких проблем с управлением им.

#include "switchable.h"
#include <iostream>

class Fan final : public Switchable 
{
  void TurnOn() override 
  {
    std::cout << "Turn on fan" << std::endl;
  }

  void TurnOff() override 
  {
    std::cout << "Turn off fan" << std::endl;
  }

};

Таким образом, используя инверсию зависимостей, вместо того, чтобы иметь классы, которые напрямую связаны, мы разделяем их с помощью интерфейса!

Теги

C++ / CppИнверсия зависимостей / Dependency InversionПаттерны проектирования / Design PatternsПринципы SOLIDПрограммирование

На сайте работает сервис комментирования DISQUS, который позволяет вам оставлять комментарии на множестве сайтов, имея лишь один аккаунт на Disqus.com.

В случае комментирования в качестве гостя (без регистрации на disqus.com) для публикации комментария требуется время на премодерацию.