Сколько инъекций допустимо в одном классе при использовании внедрения зависимостей

9

Я использую Unity в C # для внедрения зависимостей, но вопрос должен быть применим для любого языка и среды, в которых используется внедрение зависимостей.

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

Например, у меня есть хранилище с 9 инъекциями. Будет ли это трудно прочитать для другого разработчика?

Инъекции имеют следующие обязанности:

  • IDbContextFactory - создание контекста для базы данных.
  • IMapper - сопоставление сущностей с моделями доменов.
  • IClock - тезисы DateTime.Now, чтобы помочь с юнит-тестами.
  • IPerformanceFactory - измеряет время выполнения для определенных методов.
  • ILog - Log4net для регистрации.
  • ICollectionWrapperFactory - создает коллекции (которые расширяют IEnumerable).
  • IQueryFilterFactory - генерирует запросы на основе входных данных, которые будут запрашивать БД.
  • IIdentityHelper - Извлекает зарегистрированного пользователя.
  • IFaultFactory - создайте разные исключения FaultException (я использую WCF).

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

Итак, мои вопросы:

Есть ли ограничение на количество инъекций в классе? И если да, то как этого избежать?

Много ли инъекций ограничивают читабельность или действительно улучшают их?

smoksnes
источник
2
Измерять качество по номерам обычно так же плохо, как платить по строкам кода, которые вы пишете в месяц. Тем не менее, вы включили реальный пример зависимостей, что является отличной идеей. Если бы я был вами, я бы переформулировал ваш вопрос, чтобы удалить концепцию подсчета и строгого ограничения и сосредоточиться вместо качества как такового. Переформулируя его, будьте осторожны, чтобы не сделать ваш вопрос слишком конкретным.
Арсений Мурзенко
3
Сколько аргументов конструктора приемлемо? IoC не меняет этого .
Теластин
Почему люди всегда хотят абсолютных ограничений?
Марьян Венема
1
@Telastyn: не обязательно. Изменения IoC заключаются в том, что вместо того, чтобы полагаться на статические классы / синглеты / глобальные переменные, все зависимости более централизованы и более «видимы».
Арсений Мурзенко
@MarjanVenema: потому что это делает жизнь намного проще. Если вы точно знаете максимальный LOC для метода или максимальное количество методов в классе или максимальное количество переменных в методе, и это единственное, что имеет значение, становится легко квалифицировать код как хороший или плохой, как а также «исправить» плохой код. К сожалению, реальная жизнь намного сложнее, и многие показатели в основном не имеют значения.
Арсений Мурзенко

Ответы:

11

Слишком много зависимостей может указывать на то, что сам класс делает слишком много. Чтобы определить, не слишком ли много:

  • Посмотрите на сам класс. Имеет ли смысл разделить его на два, три, четыре? Имеет ли это смысл в целом?

  • Посмотрите на типы зависимостей. Какие из них являются предметно-ориентированными, а какие «глобальными»? Например, я бы не рассматривал ILogна том же уровне, что IQueryFilterFactoryи первый: в любом случае, он будет доступен в большинстве бизнес-классов, если они используют ведение журнала. С другой стороны, если вы обнаружите много зависимых от домена зависимостей, это может указывать на то, что класс делает слишком много.

  • Посмотрите на зависимости, которые можно заменить значениями.

    IClock - тезисы DateTime.Now, чтобы помочь с юнит-тестами.

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

Глядя на реальные зависимости, я не вижу ничего, что указывало бы на то, что происходили плохие вещи:

  • IDbContextFactory - создание контекста для базы данных.

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

  • IMapper - сопоставление сущностей с моделями доменов.

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

    Другой вариант - разделить класс на две части: одна связана с отображением, другая - с реальной бизнес-логикой. Это создаст слой де-факто, который будет дальше отделять BL от DAL. Если сопоставления сложны, это может быть хорошей идеей. Однако в большинстве случаев это просто добавит бесполезную сложность.

  • IClock - тезисы DateTime.Now, чтобы помочь с юнит-тестами.

    Вероятно, не очень полезно иметь отдельный интерфейс (и класс), чтобы узнать текущее время. Я бы просто передал DateTime.Nowметоды, которые требуют текущего времени.

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

  • IPerformanceFactory - измеряет время выполнения для определенных методов.

    Смотрите следующий пункт.

  • ILog - Log4net для регистрации.

    Такая трансцендентная функциональность должна принадлежать платформе, а фактические библиотеки должны быть взаимозаменяемыми и настраиваемыми во время выполнения (например, через app.config в .NET).

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

    Если библиотека слишком сложна в использовании, имеет смысл использовать шаблон фасада.

  • ICollectionWrapperFactory - создает коллекции (которые расширяют IEnumerable).

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

  • IQueryFilterFactory - генерирует запросы на основе входных данных, которые будут запрашивать БД.

    Почему это не на уровне доступа к данным? Почему Filterв названии есть?

  • IIdentityHelper - Извлекает зарегистрированного пользователя.

    Я не уверен, почему существует Helperсуффикс. Во всех случаях другие суффиксы также не будут особенно явными ( IIdentityManager?)

    Во всяком случае, имеет смысл иметь эту зависимость здесь.

  • IFaultFactory - создайте разные исключения FaultException (я использую WCF).

    Это логика настолько сложна, что требует использования заводского шаблона? Почему для этого используется инъекция зависимости? Вы бы поменялись местами при создании исключений между рабочим кодом и тестами? Почему?

    Я бы попробовал перестроить это на простое throw new FaultException(...). Если некоторая глобальная информация должна быть добавлена ​​ко всем исключениям перед распространением их на клиент, WCF, вероятно, имеет механизм, где вы перехватываете необработанное исключение и можете изменить его и повторно отправить его клиенту.

Есть ли ограничение на количество инъекций в классе? И если да, то как этого избежать?

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

Много ли инъекций ограничивают читабельность или действительно улучшают их?

Множество зависимостей усложняют соблюдение логики. Если логике трудно следовать, класс, вероятно, делает слишком много и его следует разделить.

Арсений Мурзенко
источник
Спасибо за ваши комментарии и ваше время. В основном о FaultFactory, которая вместо этого будет перемещена в WCF-логику. Я сохраню IClock, так как это спасает жизнь при TDD-приложении. Есть несколько раз, когда вы хотите убедиться, что определенное значение было установлено с заданным временем. Тогда DateTime.Now не всегда будет достаточным, так как он не является надёжным.
Smoksnes
7

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

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

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

Чтобы ответить на ваши последние вопросы:

  • Существует ли верхний предел для количества зависимостей, которые должен иметь класс?

Да, верхний предел "слишком много". Сколько "слишком много"? «Слишком много» - это когда сплоченность класса становится «слишком низкой». Все это зависит. Обычно, если ваша реакция на класс - «Ух, у этой вещи много зависимостей», это слишком много.

  • Улучшает ли зависимость от инъекций или ухудшает читабельность?

Я думаю, что этот вопрос вводит в заблуждение. Ответ может быть да или нет. Но это не самая интересная часть. Смысл внедрения зависимостей состоит в том, чтобы сделать их ВИДИМЫМИ. Речь идет о создании API, который не лжет. Это о предотвращении глобального состояния. Речь идет о том, чтобы сделать код тестируемым. Речь идет о снижении сцепления.

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

Сара
источник
1
Хороший отзыв. Спасибо. Да, верхний предел "слишком много". - Фантастика и так правда.
Smoksnes
3

Согласно Стиву Макконнелу, автору вездесущей «Code Complete», практическое правило гласит, что более 7 - это запах кода, который ухудшает удобство обслуживания. Лично я считаю, что в большинстве случаев это число ниже, но правильное выполнение DI приведет к тому, что в него будет добавлено много зависимостей, когда вы очень близки к корню композиции. Это нормально и ожидаемо. Это одна из причин, по которой контейнеры IoC полезны и стоят той сложности, которую они добавляют в проект.

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

Резиновая утка
источник