В чем практическая разница между стилями внедрения зависимостей?

12

Я новичок в внедрении зависимостей, и у меня есть несколько вопросов о том, какой стиль я должен использовать в своих приложениях. Я только что прочитал статью Мартин Фаулер « Инверсия контейнеров управления» и «Шаблон внедрения зависимостей », но не могу понять практической разницы между инжектором конструктора, сеттера и интерфейса.

Мне кажется, что причины использования одного над другим - просто вопрос очистки кода и / или ясности. В чем разница? Есть ли какие-либо сильные преимущества или недостатки использования одного над другим, или это только то, что я уже говорил ранее?

На мой взгляд, внедрение конструктора является наиболее интуитивным, а внедрение интерфейса - наименьшим. С другой стороны, внедрение метода является средним термином, но должны ли вы изменять экземпляр объекта зависимости, который вы изначально внедрили? Гарантирует ли этот стиль внедрения, что объект, который нуждается в зависимости, всегда будет внедряться? Я не верю, но, пожалуйста, поправьте меня, если я ошибаюсь.

ecampver
источник
Не уверен, что еще ты нашел или прочитал по пути. Я нашел несколько похожих тем здесь и здесь . Я сам новичок, и мне было бы интересно узнать ответы, которые вы можете получить :)
@ user1766760, ты должен мне помочь, вот, мой вопрос голосуется за закрытие, еще два голоса, и все готово !!! Голосуйте за вопрос или что-то в этом роде, я не знаю, что можно сделать, чтобы избежать закрытия.
ecampver
хм ... я новичок в SO, поэтому я не слишком уверен, как я могу помочь / почему за него проголосовали за закрытие (я не вижу никаких признаков ??). Если я рискну предположить, возможно, это не конкретный вопрос программирования, а скорее обсуждение?
Вы могли бы немного расширить вопрос. Внедрение зависимостей является одной из форм «инверсии контроля». Есть альтернативы. Полезной, но готовой к злоупотреблению альтернативой, например, является Расположение службы.
Ян

Ответы:

12

Преимущество Constructor Injection заключается в том, что оно делает зависимость явной и вынуждает клиента предоставлять экземпляр. Это также может гарантировать, что клиент не сможет изменить экземпляр позже. Одним (возможным) недостатком является то, что вам нужно добавить параметр в ваш конструктор.

Преимущество Setter Injection заключается в том, что он не требует добавления параметра в конструктор. Это также не требует, чтобы клиент установил экземпляр. Это полезно для необязательных зависимостей. Это также может быть полезно, если вы хотите, чтобы класс создавал, например, реальное хранилище данных по умолчанию, а затем в тесте вы можете использовать установщик, чтобы заменить его экземпляром тестирования.

Интерфейсная инъекция , насколько я могу судить, не сильно отличается от настройки инъекции. В обоих случаях вы (необязательно) устанавливаете зависимость, которую можно изменить позже.

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

Часто моя единственная причина перехода на две отдельные реализации - это тестирование. В производстве я могу перейти в a DataRepository, но в тестировании я бы прошел в FakeDataRepository. В этом случае я обычно предоставляю два конструктора: один без параметров, а другой принимает a IDataRepository. Затем в конструкторе без параметров я буду связывать вызов второго конструктора и передавать в new DataRepository().

Вот пример в C #:


public class Foo
{
  private readonly IDataRepository dataRepository;

  public Foo() : this(new DataRepository())
  {
  }

  public Foo(IDataRespository dataRepository)
  {
    this.dataRepository = dataRepository;
  }
}

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

var foo = new Foo(new DataRepository());
Тем не менее, я все еще могу передать альтернативную реализацию для тестирования. Я понимаю, что с DI Бедного Человека я жестко программирую свою зависимость, но это приемлемо для меня, так как я в основном использую DI для тестирования

jhewlett
источник
спасибо, это довольно близко к тому, что я понимаю, но все же, есть ли сценарий, где вы не можете использовать инжектор конструктора ?
Eampver
1
Вы, вероятно, не захотите использовать конструктор для необязательных зависимостей. Я не могу думать ни о каких других причинах, чтобы не использовать это.
Jhewlett
Я специально использую внедрение метода установки свойств, чтобы заполнить некоторые классы конфигурации, которые содержат большое количество значений. Я не использую это где-нибудь еще. Я всегда немного сомневаюсь в обосновании необязательных параметров, потому что есть большая вероятность, что это нарушение единого правила ответственности. Конечно, правила должны быть нарушены, так что ...
Ян
@jhewlett - это потрясающе, я искал хорошую простую технику окурков для модульного тестирования, которая не слишком усложнит мое решение - и я думаю, что это так :)
Rocklan
2

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

Внедрение интерфейса - это более продвинутая форма внедрения, которая полезна, поскольку позволяет определять зависимость в момент ее использования, а не во время инициализации объекта, который будет ее использовать. Это позволяет использовать несколько полезных опций:

  • Зависимость может быть по-разному ограничена объектом, в который она вводится; Например, вы можете использовать внедрение интерфейса для предоставления объекта, для которого он существует, для сеанса пользователя или для потока в глобальном синглтоне. Каждый раз, когда объект нуждается в зависимости, он будет вызывать метод getter, предоставляемый платформой, и это может возвращать разные результаты в зависимости от ситуации, в которой он вызывается.

  • Это допускает ленивую инициализацию - нет необходимости инициализировать зависимость, пока она не собирается использоваться

  • Это позволяет загружать зависимости из кэшированной копии, когда они существуют, или переинициализировать, когда их нет (например, с помощью SoftReferenceв Java).

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

Жюль
источник