Мне не нравится инъекция зависимостей на основе конструктора.
Я считаю, что это увеличивает сложность кода и снижает ремонтопригодность, и я хотел бы знать, существуют ли какие-либо жизнеспособные альтернативы.
Я не говорю о концепции ветки реализации от интерфейса и возможности динамического решения (рекурсивно) набора объектов из интерфейса. Я полностью поддерживаю это. Тем не менее, традиционный конструктор, основанный на этом, кажется, имеет несколько проблем.
1) Все тесты зависят от конструкторов.
После интенсивного использования DI в проекте MVC 3 С# за последний год я нахожу наш код таким, как это:
public interface IMyClass {
...
}
public class MyClass : IMyClass {
public MyClass(ILogService log, IDataService data, IBlahService blah) {
_log = log;
_blah = blah;
_data = data;
}
...
}
Проблема: если мне нужна другая служба в моей реализации, мне нужно изменить конструктор; и это означает, что все модульные тесты для этого класса прерываются.
Даже те тесты, которые не имеют ничего общего с новой функциональностью, требуют, по крайней мере, рефакторинга, чтобы добавить дополнительные параметры и ввести макет для этого аргумента.
Это похоже на небольшую проблему, а также автоматические инструменты, такие как помощь resharper, но это, безусловно, раздражает, когда простые изменения, подобные этому, приводят к потере более 100 тестов. Практически я видел, как люди делали немые вещи, чтобы избежать изменения конструктора, а не укусить пулю и исправить все тесты, когда это произойдет.
2) Служебные экземпляры передаются без необходимости, увеличивая сложность кода.
public class MyWorker {
public MyWorker(int x, IMyClass service, ILogService logs) {
...
}
}
Создание экземпляра этого класса, если это возможно только в том случае, если я попал в контекст, где данные службы доступны и автоматически были разрешены (например, контроллер) или, к несчастью, путем передачи экземпляра службы по нескольким целям вспомогательных классов.
Я вижу такой код все время:
public class BlahHelper {
// Keep so we can create objects later
var _service = null;
public BlahHelper(IMyClass service) {
_service = service;
}
public void DoSomething() {
var worker = new SpecialPurposeWorker("flag", 100, service);
var status = worker.DoSomethingElse();
...
}
}
В случае, если этот пример не ясен, я говорю о передаче разрешенных экземпляров интерфейса DI вниз через несколько слоев от каких-либо причин, кроме как на нижнем уровне, который им нужно внедрить во что-то.
Если класс не зависит от службы, он должен иметь зависимость от этой службы. Эта идея о том, что существует "переходная" зависимость, когда класс не использует услугу, а просто передает ее, - это, на мой взгляд, глупость.
Однако я не знаю лучшего решения.
Есть ли что-то, что обеспечивает преимущества DI без этих проблем?
Я предполагал использовать структуру DI внутри конструкторов, так как это устраняет пару проблем:
public MyClass() {
_log = Register.Get().Resolve<ILogService>();
_blah = Register.Get().Resolve<IBlahService>();
_data = Register.Get().Resolve<IDataService>();
}
Есть ли недостатки в этом?
Это означает, что модульные тесты должны иметь "предварительное знание" класса для привязки mocks для правильных типов во время тестирования init, но я не вижу других недостатков.
NB. Мои примеры находятся в С#, но я тоже наткнулся на те же проблемы на других языках, и особенно языки с менее зрелой инструментальной поддержкой - это главные головные боли.