Практика внедрения зависимостей / контейнера IoC при написании фреймворков

18

Я использовал различные контейнеры IoC (Castle.Windsor, Autofac, MEF и т. Д.) Для .Net в ряде проектов. Я обнаружил, что они часто подвергаются насилию и поощряют ряд плохих практик.

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

Например, несколько запахов кода, которые я заметил, и у которых нет хороших предложений:

  1. Большое количество параметров (> 5) для конструкторов. Создание услуг имеет тенденцию быть сложным; все зависимости внедряются через конструктор - несмотря на то, что компоненты редко бывают необязательными (за исключением, может быть, при тестировании).

  2. Отсутствие частных и внутренних занятий; это может быть конкретным ограничением использования C # и Silverlight, но мне интересно, как это решить. Трудно сказать, что такое интерфейс фреймворков, если все классы общедоступны; это позволяет мне получить доступ к частным частям, которые я, вероятно, не должен касаться.

  3. Связь жизненного цикла объекта с контейнером IoC. Часто трудно вручную построить зависимости, необходимые для создания объектов. Жизненный цикл объекта слишком часто управляется платформой IoC. Я видел проекты, где большинство классов зарегистрированы как синглтоны. Вы получаете отсутствие явного контроля, а также вынуждены управлять внутренними компонентами (это относится к вышеприведенному пункту, все классы общедоступны, и вы должны вводить их).

Например, .Net Framework имеет много статических методов. например, DateTime.UtcNow. Много раз я видел это завернутым и введенным в качестве параметра конструкции.

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

Как предоставить как тестируемый интерфейс, так и простой в использовании? Каковы лучшие практики?

Дэйв Хиллиер
источник
1
Какие плохие практики они поощряют? Что касается частных классов, вам не нужно иметь их очень много, если у вас хорошая инкапсуляция; с одной стороны, их гораздо сложнее проверить.
Aaronaught
Err, 1 и 2 плохие практики. Большие конструкторы и не использовать инкапсуляцию для создания минимального интерфейса? Кроме того, я сказал частные и внутренние (используйте внутренние, видимые для, чтобы проверить). Я никогда не использовал фреймворк, который заставляет меня использовать определенный контейнер IoC.
Дэйв Хиллиер,
2
Возможно ли, что признаком необходимости множества параметров для конструкторов вашего класса сам по себе является запах кода, а DIP только делает это более очевидным?
dreza
3
Использование определенного контейнера IoC в среде обычно не является хорошей практикой. Использование внедрения зависимостей является хорошей практикой. Вы знаете о разнице?
Aaronaught
1
@Aaronaught (вернулся из мертвых). Использование «внедрение зависимостей - хорошая практика» неверно. Внедрение зависимостей - это инструмент, который следует использовать только при необходимости. Постоянное использование зависимостей - плохая практика.
Дейв Хиллиер,

Ответы:

27

Единственный законный анти-шаблон внедрения зависимостей, о котором я знаю, - это шаблон Service Locator , который является анти-шаблоном, когда для него используется структура DI.

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

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

  • Внедрение данных, в отличие от поведения, является подтипом анти-паттерна полтергейста , в данном случае «геист» является контейнером. Если класс должен знать текущую дату и время, вы не вводите a DateTime, который является данными; вместо этого вы вводите абстракцию через системные часы (я обычно называю мои ISystemClock, хотя я думаю, что есть более общая в проекте SystemWrappers ). Это не только правильно для DI; это абсолютно необходимо для тестируемости, так что вы можете тестировать изменяющиеся во времени функции без необходимости их фактического ожидания.

  • Объявление каждого жизненного цикла Синглтоном является для меня отличным примером программирования культа грузов и, в меньшей степени, разговорно названной « выгребной ямы объектов ». Я видел больше злоупотреблений синглтоном, чем хотел бы вспомнить, и очень мало из них связано с DI.

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

  • Последняя ошибка, которую я обычно вижу, это «необязательная зависимость», которую они сделали в NerdDinner . Другими словами, есть конструктор , который принимает инъекции зависимостей, но и другой конструктор , который использует реализацию « по умолчанию». Это также нарушает DIP и имеет тенденцию приводить к LSP нарушений , а также, как разработчики, в течение долгого времени, начинают делать предположения вокруг реализации по умолчанию, и / или начать новые-Инг до экземпляров с помощью конструктора по умолчанию.

Как гласит старая поговорка, вы можете писать на Фортране на любом языке . Dependency Injection не серебряная пуля , которая будет препятствовать разработчикам завинчивания их управления зависимостями, но это действительно предотвратить ряд распространенных ошибок / анти-шаблоны:

...и так далее.

Очевидно, что вы не хотите разрабатывать структуру, зависящую от конкретной реализации контейнера IoC , такой как Unity или AutoFac. То есть, еще раз, нарушая DIP. Но если вы даже задумываетесь над тем, чтобы сделать что-то подобное, то вы, должно быть, уже допустили несколько ошибок проектирования, потому что Dependency Injection - это универсальный метод управления зависимостями, который не связан с концепцией контейнера IoC.

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

Я не ракетостроение. Просто старайтесь избегать newи staticкроме случаев , когда есть веские причины , чтобы использовать их, например, метод полезности , который не имеет внешних зависимостей, или служебный класс , который не мог бы иметь какой - либо цели вне рамок (Interop оберток и ключи словаря являются общими примерами это).

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

Aaronaught
источник
У меня есть несколько вопросов. Для библиотеки, распространяемой в NuGet, я не могу зависеть от системы IoC, поскольку это делается приложением, использующим библиотеку, а не самой библиотекой. Во многих случаях пользователь библиотеки действительно не заботится о ее внутренних зависимостях вообще. Есть ли проблема с конструктором по умолчанию, инициирующим внутренние зависимости, и более длинным конструктором для модульного тестирования и / или пользовательских реализаций? Также вы говорите, чтобы избежать "нового". Это относится к таким вещам, как StringBuilder, DateTime и TimeSpan?
Этьен Чарленд