Нужно ли вводить зависимости в ctor или для метода?

16

Рассмотреть возможность:

public class CtorInjectionExample
{
    public CtorInjectionExample(ISomeRepository SomeRepositoryIn, IOtherRepository OtherRepositoryIn)
    {
        this._someRepository = SomeRepositoryIn;
        this._otherRepository = OtherRepositoryIn;
    }

    public void SomeMethod()
    {
        //use this._someRepository
    }

    public void OtherMethod()
    {
        //use this._otherRepository
    }
}

против:

public class MethodInjectionExample
{
    public MethodInjectionExample()
    {
    }

    public void SomeMethod(ISomeRepository SomeRepositoryIn)
    {
        //use SomeRepositoryIn
    }

    public void OtherMethod(IOtherRepository OtherRepositoryIn)
    {
        //use OtherRepositoryIn
    }
}

Хотя внедрение Ctor затрудняет расширение (любой код, вызывающий ctor, будет нуждаться в обновлении при добавлении новых зависимостей), и внедрение уровня метода, похоже, остается более инкапсулированным из зависимости уровня класса, и я не могу найти никаких других аргументов за / против этих подходов ,

Есть ли определенный подход для инъекций?

(NB. Я искал информацию по этому вопросу и пытался сделать этот вопрос объективным.)

StuperUser
источник
4
Если ваши классы связны, то многие разные методы будут использовать одни и те же поля. Это должно дать вам ответ, который вы ищете ...
Одед
@ Хорошо, сплоченность сильно повлияет на решение. Если, скажем, класс представляет собой наборы вспомогательных методов, каждый из которых имеет разные зависимости (кажущиеся не связными, но логически схожими), а другой является весьма связным, то каждый из них должен использовать наиболее выгодный подход. Однако это может привести к несогласованности решения.
StuperUser
Относится к приведенным мною примерам: en.wikipedia.org/wiki/False_dilemma
StuperUser

Ответы:

3

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

NOHROS
источник
15

Хорошо, во-первых, давайте разберемся с «Любой код, вызывающий ctor, будет нуждаться в обновлении при добавлении новых зависимостей»; Для ясности, если вы делаете внедрение зависимостей и у вас есть какой-либо код, вызывающий new () для объекта с зависимостями, вы делаете это неправильно .

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

Что касается идеи внедрения метода по сравнению с методом в классе, то есть две основные проблемы, связанные с внедрением метода.

Одна из проблем заключается в том, что методы вашего класса должны совместно использовать зависимости. Это один из способов обеспечить эффективное разделение классов: если вы видите класс с большим количеством зависимостей (вероятно, более 4-5), то этот класс является основным кандидатом. для рефакторинга на два класса.

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

var someDependency = ServiceLocator.Resolve<ISomeDependency>();
var something = classBeingInjected.DoStuff(someDependency);

Теперь предположим, что вы собираетесь вызывать этот метод в 10 местах вашего приложения: у вас будет 10 таких фрагментов. Далее, скажем, вам нужно добавить еще одну зависимость в DoStuff (): вам придется изменить этот фрагмент 10 раз (или обернуть его в методе, и в этом случае вы просто реплицируете поведение DI вручную, что является пустой тратой) времени).

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

Сравните это с инжектором конструктора; При внедрении в конструктор вы не привязаны к конкретному контейнеру DI и никогда не несете прямой ответственности за выполнение зависимостей ваших классов, поэтому обслуживание практически не требует головной боли.

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

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

Эд Джеймс
источник
1
"if you're doing dependency injection and you have any code calling new() on an object with dependencies, you're doing it wrong."Если вы проводите модульное тестирование метода в классе, вы должны создать его экземпляр или используете DI для создания экземпляра тестируемого кода?
StuperUser
@StuperUser Модульное тестирование - это немного другой сценарий, мой комментарий действительно относится только к рабочему коду. Поскольку вы будете создавать фиктивные (или заглушки) зависимости для своих тестов с помощью фиктивной среды, каждая с предварительно настроенным поведением и набором предположений и утверждений, вам нужно будет вводить их вручную.
Эд Джеймс
1
Это код, о котором я говорю, когда говорю "any code calling the ctor will need updating", что мой рабочий код не вызывает ctors. По этим причинам.
StuperUser
2
@StuperUser Справедливо, но вы все равно будете вызывать методы с необходимыми зависимостями, что означает, что остальная часть моего ответа остается в силе! Честно говоря, я согласен с проблемой внедрения инжекторов и форсирования всех зависимостей с точки зрения модульного тестирования. Я склонен использовать внедрение свойств, так как это позволяет вам вводить только те зависимости, которые, как вы ожидаете, понадобятся для теста, который я нахожу в сущности другим набором неявных вызовов Assert.WasNotCalled без необходимости фактически писать какой-либо код! : D
Эд Джеймс
3

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

Ответ: это зависит от необходимости. Иногда лучше использовать один вид, а другой - другой.

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

BЈовић
источник
3

Вы пропустили ту, которая, по крайней мере для меня, является самой гибкой - инъекция свойства. Я использую Castle / Windsor, и все, что мне нужно сделать, это добавить новое свойство auto в свой класс, и я получаю внедренный объект без каких-либо проблем и без нарушения интерфейсов.

Otávio Décio
источник
Я привык к веб-приложениям, и если эти классы находятся в слое «Домен», вызываемом пользовательским интерфейсом, чьи зависимости уже разрешены аналогично Property Injection. Мне интересно, как обращаться с классами, которым я могу передать введенные объекты.
StuperUser
Мне также нравится внедрение свойств, просто чтобы вы все еще могли использовать обычные параметры конструктора с контейнером DI, автоматически различая разрешенные и неразрешенные зависимости. Однако, поскольку конструктор и внедрение свойств функционально идентичны для этого сценария, я решил не упоминать об этом;)
Эд Джеймс
Внедрение свойства заставляет каждый объект быть изменчивым и создает экземпляры в недопустимом состоянии. Почему это было бы предпочтительнее?
Крис Питман
2
@Chris На самом деле, при нормальных условиях инжекция конструктора и свойств действует почти одинаково, когда объект полностью внедряется во время разрешения. Единственный раз, когда его можно считать «недействительным», это во время модульного тестирования, когда вы не собираетесь использовать контейнер DI для создания экземпляра реализации. См. Мой комментарий к моему ответу на мой взгляд, почему это на самом деле полезно. Я ценю, что изменчивость может быть проблемой, но реально вы будете ссылаться только на разрешенные классы через их интерфейсы, которые не будут выставлять зависимости для редактирования.
Эд Джеймс