Я использую Unity в C # для внедрения зависимостей, но вопрос должен быть применим для любого языка и среды, в которых используется внедрение зависимостей.
Я пытаюсь следовать принципу SOLID, и поэтому я получил много абстракций. Но теперь я задаюсь вопросом, есть ли лучшая практика для того, сколько инъекций должен сделать один класс?
Например, у меня есть хранилище с 9 инъекциями. Будет ли это трудно прочитать для другого разработчика?
Инъекции имеют следующие обязанности:
- IDbContextFactory - создание контекста для базы данных.
- IMapper - сопоставление сущностей с моделями доменов.
- IClock - тезисы DateTime.Now, чтобы помочь с юнит-тестами.
- IPerformanceFactory - измеряет время выполнения для определенных методов.
- ILog - Log4net для регистрации.
- ICollectionWrapperFactory - создает коллекции (которые расширяют IEnumerable).
- IQueryFilterFactory - генерирует запросы на основе входных данных, которые будут запрашивать БД.
- IIdentityHelper - Извлекает зарегистрированного пользователя.
- IFaultFactory - создайте разные исключения FaultException (я использую WCF).
Я не очень разочарован тем, как я делегировал обязанности, но я начинаю беспокоиться за удобочитаемость.
Итак, мои вопросы:
Есть ли ограничение на количество инъекций в классе? И если да, то как этого избежать?
Много ли инъекций ограничивают читабельность или действительно улучшают их?
источник
Ответы:
Слишком много зависимостей может указывать на то, что сам класс делает слишком много. Чтобы определить, не слишком ли много:
Посмотрите на сам класс. Имеет ли смысл разделить его на два, три, четыре? Имеет ли это смысл в целом?
Посмотрите на типы зависимостей. Какие из них являются предметно-ориентированными, а какие «глобальными»? Например, я бы не рассматривал
ILog
на том же уровне, чтоIQueryFilterFactory
и первый: в любом случае, он будет доступен в большинстве бизнес-классов, если они используют ведение журнала. С другой стороны, если вы обнаружите много зависимых от домена зависимостей, это может указывать на то, что класс делает слишком много.Посмотрите на зависимости, которые можно заменить значениями.
Это может быть легко заменено
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, вероятно, имеет механизм, где вы перехватываете необработанное исключение и можете изменить его и повторно отправить его клиенту.Измерять качество по номерам обычно так же плохо, как платить по строкам кода, которые вы пишете в месяц. У вас может быть большое количество зависимостей в хорошо спроектированном классе, так как у вас может быть дрянной класс, использующий несколько зависимостей.
Множество зависимостей усложняют соблюдение логики. Если логике трудно следовать, класс, вероятно, делает слишком много и его следует разделить.
источник
Это классический пример того, как DI говорит вам, что ваш класс, вероятно, становится слишком большим, чтобы быть одним классом. Это часто интерпретируется как «вау, DI делает мои конструкторы больше, чем Юпитер, эта техника ужасна», но на самом деле она говорит вам, что «ваш класс имеет множество зависимостей». Зная это, мы можем либо
Существует бесчисленное множество способов управления зависимостями, и невозможно сказать, что работает лучше всего в вашем случае, не зная ваш код и ваше приложение.
Чтобы ответить на ваши последние вопросы:
Да, верхний предел "слишком много". Сколько "слишком много"? «Слишком много» - это когда сплоченность класса становится «слишком низкой». Все это зависит. Обычно, если ваша реакция на класс - «Ух, у этой вещи много зависимостей», это слишком много.
Я думаю, что этот вопрос вводит в заблуждение. Ответ может быть да или нет. Но это не самая интересная часть. Смысл внедрения зависимостей состоит в том, чтобы сделать их ВИДИМЫМИ. Речь идет о создании API, который не лжет. Это о предотвращении глобального состояния. Речь идет о том, чтобы сделать код тестируемым. Речь идет о снижении сцепления.
Хорошо разработанные классы с разумно разработанными и именованными методами более читабельны, чем плохо разработанные. DI на самом деле не улучшает и не ухудшает читабельность как таковую, он просто выделяет ваш выбор дизайна, и если он плохой, он жалит ваши глаза. Это не означает, что DI сделал ваш код менее читабельным, это просто показало вам, что ваш код уже был беспорядочным, вы просто его спрятали.
источник
Согласно Стиву Макконнелу, автору вездесущей «Code Complete», практическое правило гласит, что более 7 - это запах кода, который ухудшает удобство обслуживания. Лично я считаю, что в большинстве случаев это число ниже, но правильное выполнение DI приведет к тому, что в него будет добавлено много зависимостей, когда вы очень близки к корню композиции. Это нормально и ожидаемо. Это одна из причин, по которой контейнеры IoC полезны и стоят той сложности, которую они добавляют в проект.
Итак, если вы очень близки к точке входа в вашу программу, это нормально и приемлемо. Если вы углубляетесь в логику программы, скорее всего, это запах, который следует устранить.
источник