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

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

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

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

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

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

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

4

Решение

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

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 Собственные объекты не уничтожаются (так называемые циклические ссылки). Однако при внедрении в конструктор циклические зависимости невозможны из-за определенного четко определенного порядка построения.

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

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

Более подробная информация:

10

Другие решения

Других решений пока нет …