Лучшие практики для внедрения зависимостей через конструктор

Инверсия управления - это метод подтверждения ценности, который используется для модульного построения системы и отделения компонентов от друг с другом.

Низкое сцепление всегда является преимуществом: оно упрощает автоматическое тестирование компонентов и улучшает соответствие кода принципу единой ответственности < / а>.

Среди способов объявления зависимости от другого класса (указатель службы, внедрение свойств, вызывающее общедоступный метод / установка общедоступного свойства ...), внедрение конструктора кажется лучшим подходом.

Хотя это, вероятно, самый сложный для реализации (по крайней мере, из трех перечисленных), он имеет значительные преимущества:

  • все зависимости действительно видны с подписью конструктора;
  • циклических зависимостей не происходит из-за четко определенного порядка создания экземпляров.

Каковы плюсы и минусы множества вариантов, которые предлагает C ++ для выполнения инъекции через конструктор?


person manlio    schedule 01.09.2016    source источник


Ответы (1)


Копируемый класс экземпляра

class object
{
public:
  object(dependency d) : dep_(d) {}

private:
  dependency dep_;
};

Работает только в том случае, если класс dependency полностью не имеет состояния, т.е. не имеет членов. На практике это случается редко, потому что класс dependency может хранить свою собственную зависимость.

Необработанный указатель

class object
{
public:
  object(dependency *d) : dep_(d)
  {
    if (d == nullptr)
      throw std::exception("null dependency");
  }

private:
  dependency *dep_;
};

Это работает как настоящая инъекция. Нам необходимо проверить переданный указатель на значение nullptr.

Класс object не владеет классом dependency, поэтому вызывающий код обязан убедиться, что object уничтожен до объекта dependency.

В реальном приложении иногда очень сложно проверить.

Ссылка

#define DISALLOW_COPY_AND_ASSIGN(Class) \
  Class(const Class &) = delete;        \
  Class &operator=(const Class &) = delete

class object
{
public:
  object(dependency &d) : dep_(d) {}

  DISALLOW_COPY_AND_ASSIGN(object);

private:
  dependency &dep_;
};

Ссылка не может быть нулевой, поэтому в этой перспективе это немного безопаснее.

Однако этот подход накладывает дополнительные ограничения на класс object: он не должен быть копируемым, поскольку ссылка не может быть скопирована. Вам нужно либо вручную переопределить оператор присваивания и конструктор копирования, чтобы остановить копирование, либо унаследовать его от чего-то вроде boost::noncopyable.

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

Если зависимость является константной ссылкой:

class object
{
public:
  object(const dependency &d) : dep_(d) {}

private:
  const dependency &dep_;
};

стоит обратить внимание на то, что класс object принимает ссылки на временные объекты:

dependency d;
object o1(d);             // this is ok, but...

object o2(dependency());  // ... this is BAD.

Дальнейшие подробности:

Умный указатель

class object
{
public:
  object(std::shared_ptr<dependency> d) : dep_(d)
  {
    if (!d)
      throw std::exception("null dependency");
  }

private:
  std::shared_ptr<dependency> dep_;
};

Подобно необработанному указателю, но право собственности контролируется механизмом интеллектуального указателя.

По-прежнему нужно проверить nullptr в теле конструктора.

Основным преимуществом является контроль времени жизни объекта dependency: вызывающему приложению не нужно должным образом контролировать порядок уничтожения (но учтите, что вам нужно будьте очень осторожны при разработке API с std::shared_ptr).

Когда класс dependency больше не используется, он автоматически уничтожается shared_ptr деструктором.

Бывают случаи, когда shared_ptr принадлежащие объекты не уничтожаются (так называемые циклические ссылки). Однако при внедрении конструктора циклические зависимости невозможны из-за конкретного четко определенного порядка построения.

Это, конечно, работает, если в приложении не используются другие методы инъекции.

Умный указатель имеет небольшие накладные расходы, но в большинстве случаев это не проблема.

Дальнейшие подробности:

person Community    schedule 01.09.2016
comment
Работает только в том случае, если класс зависимостей полностью не имеет состояния, то есть не имеет членов. Не могли бы вы это объяснить? - person juanchopanza; 01.09.2016
comment
Отсутствует одна полезная версия: std::unique_ptr<dependency>. - person Kerrek SB; 01.09.2016
comment
Какой смысл размещать ответ вместе с вопросом? - person rustyx; 01.09.2016
comment
Я бы предложил std::shared_ptr<const dependency> вместо неконстантной версии. - person juanchopanza; 01.09.2016
comment
@juanchopanza Я изменил ответ на вики сообщества. Его нужно улучшать разными способами (пожалуйста, исправляйте мои ошибки). Мне нужна была отправная точка для сбора / документирования нескольких техник. - person manlio; 01.09.2016
comment
@KerrekSB Я изменил ответ на вики сообщества. Его нужно улучшать разными способами (пожалуйста, исправляйте мои ошибки). Мне нужна была отправная точка для сбора / документирования нескольких техник. - person manlio; 01.09.2016