Инъекция зависимостей-что делать, когда у вас много зависимостей?
у меня есть класс A, который зависит от 10 других классов. Согласно шаблону инъекции зависимостей, я должен передать все зависимости A его конструктором.
Итак, предположим, что этот конструктор (конечно, это не рабочий или реальный код, так как мне не разрешено публиковать реальный код здесь)
public ClassA(ClassB b, ClassC c, ClassD d, ClassE e, ClassF f, ClassG g, ClassH h, ClassI i) {
this.b = b;
this.c = c;
this.d = d;
this.e = e;
this.f = f;
this.g = g;
this.h = h;
this.i = i;
}
Я читал в книге Мартина Фаулера о рефакторинге что метод с большим количеством параметров-это запах кода и не должно произойти.
мой вопрос: это нормально, когда мы говорим о Ди? Есть ли лучший способ введения зависимостей без нарушения правил Мартина Фаулера?
Я знаю, что могу передать зависимости через свойства, но это может вызвать ошибки, так как никто не уверен, что должно быть передано, чтобы класс работал.
редактировать
Спасибо за все ваши ответы. Теперь я попытаюсь продемонстрировать некоторые зависимости класса A:
1 - класс доступ к БД
2-Другой класс для доступа к другой БД (да, мне нужно выполнить операции над двумя базами данных)
3 - класс для отправки уведомлений об ошибках по электронной почте
4 - класс, для загрузки конфигураций
5-класс, который будет действовать как таймер для некоторых операций (возможно, этого можно избежать)
6 - класс с бизнес-логикой
есть много других, от которых я пытаюсь избавиться, но они действительно необходимы, и я не вижу никаких способов избежать их.
редактировать
после некоторого рефакторинга теперь у меня есть 7 зависимостей (вниз от 10). Но у меня есть 4 DAO
объекты:
CustomerDAO
ProcessDAO
ProductsDAO
CatalogDAO
правильно ли создать другой класс под названием MyProjectDAO и ввести на него эти DAOS? Таким образом у меня будет только один класс DAO, который объединяет все объекты DAO моего проекта. Я не думаю, что это хорошая идея, потому что это нарушает принцип единой ответственности. Я прав?
4 ответов
можете ли вы оправдать (для себя), почему класс зависит от 10 других классов? Существуют ли переменные-члены, которые вы используете для связывания подмножества этих классов? Если это так, это указывает на то, что этот класс должен быть разбит так, чтобы извлеченный класс зависел от подмножества и переменных, которые связывают такое состояние вместе, идет в извлеченном классе. С 10 зависимостями возможно, что этот класс просто вырос слишком большим и в любом случае должен быть разбит на внутренние части.
A обратите внимание на ваше последнее предложение: такая зависимость порядка также может быть запахом кода, поэтому, вероятно, хорошо не выставлять его в вашем интерфейсе. Фактически, рассмотрите, являются ли требования к порядку из-за того, что операции должны выполняться в определенном порядке (это сложность алгоритма или протокола), или потому, что вы разработали свои классы взаимозависимыми. Если сложность связана с вашим дизайном, рефакторинг для устранения упорядоченной зависимости, где вероятный.
Если вы не можете рефакторировать (сложности все необходимы, и у вас просто ужасная проблема координации на ваших руках), то вы можете абстрагировать уродство и держать пользователей этого класса экранированными (строитель, завод, инжектор и т. д.).
Edit: теперь, когда я подумал об этом, я не уверен, что существенные сложности вашего алгоритма или протокола не могут быть абстрагированы немного (хотя это может быть так). В зависимости от вашей конкретной проблемы, сходства в манипуляциях этих зависимых классов могут быть лучше решены с помощью шаблона стратегии или шаблона наблюдателя (прослушивателей событий). Возможно, вам придется обернуть эти классы в классы, которые адаптируют их к немного отличающимся интерфейсам, чем то, что они предоставляют в настоящее время. Вам нужно будет оценить компромисс с тем, чтобы код в этом классе монстров стал более читаемым (yay) за счет до 10 дополнительных классов в вашем проекте (boo).
Я также хотел бы сделать добавление к абстрагированию конструкции этого класса. Кажется важным, чтобы любой класс, зависящий от этого класса, также использовал шаблон инъекции зависимостей. Таким образом, если вы используете строитель, завод, инжектор и т. д. вы не случайно лишаете себя некоторых преимуществ использования шаблона DI (самое важное, на мой взгляд, - это способность заменять макет объектов для тестирования).
Edit 2 (на основе вашего редактирования):
моя первая мысль: "что, нет регистрации зависимость?":)
даже зная, каковы зависимости, трудно предложить полезные советы.
во-первых: каковы обязанности каждого? Почему этот класс зависит от кода контроллера (бизнес-логика) и от кода модели (два разных класса доступа к базе данных с классами DAO)?
в зависимости от классов доступа DAOs и DB запах кода. Какова цель Дао? Какова цель классов БД? Вы пытаетесь работать на нескольких уровнях абстракции?
один из принципов OO заключается в том, что данные и поведение объединяются в маленькие вещи, называемые классами. Вы нарушили это, когда создали этот класс бизнес-логики, отличный от объектов, которыми он управляет, отличный от DAO, отличного от этого класса? Связанный: возьмите краткое отвлечение в твердый.
во-вторых: класс для загрузки конфигураций. Плохо пахнет. Инъекция зависимостей помогает идентифицировать зависимости и поменяй их местами. Ваш класс монстра, который зависит от определенных параметров. Эти параметры группируются в этот класс конфигурации, потому что...? Как называется этот класс конфигурации? Это DBparameters? если это так, то он принадлежит объекту(объектам) БД, а не этому классу. Является ли он общим, как конфигурации? Если это так, у вас есть мини-инжектор зависимостей (конечно, это, вероятно, только инъекция строковых или int-значений вместо составных данных, таких как классы, но почему?). Неловко.
третье: самый важный урок, который я извлек из рефакторинга, заключался в том, что мой код отстой. Мало того, что мой код сосал, но не было ни одного преобразования, чтобы заставить его перестать сосать. Лучшее, на что я мог надеяться, это сделать его менее отстойным. Как только я это сделаю, я снова смогу сделать его менее отстойным. И еще. Некоторые шаблоны дизайна плохи, но они существуют, чтобы позволить вашему отстойному коду перейти к менее отстойному коду. Поэтому вы берете глобалы и делаете их синглетами. Тогда вам устранить ваши одиночки. Не расстраивайтесь, потому что вы просто переработан, чтобы найти, что ваш код все еще хреново. Это отстой меньше. Таким образом, объект загрузки конфигурации может пахнуть, но вы можете решить, что это не самая пахучая часть вашего кода. На самом деле, вы можете обнаружить, что усилия "исправить" это не стоит.
мой опыт:
- попробовать чтобы создать свой класс, ему нужно меньше зависимостей. Если ему нужно так много, у него может быть слишком много обязанностей.
- Если вы действительно убеждены, что ваш дизайн класса подходит, подумайте, имеет ли смысл для некоторых из этих зависимостей объединяться (например, через адаптер, который берет на себя ответственность за одну "большую" операцию, необходимую вашему классу, делегируя несколько зависимостей). Вы затем может зависеть от адаптера вместо" меньших " зависимостей.
- Если каждый другой бит действительно имеет смысл, просто проглотите запах, имеющий много параметров. Такое иногда случается.
Да-метод, принимающий такое количество параметров, следует считать запахом кода. Действительно ли этот метод делает только одно и только одно?
Если это все еще верно, вы можете еще понизьте количество зависимостей, посмотрев на отношения между зависимостями - есть ли какие-либо из них тесно связаны, могут ли они быть связаны в агрегированные зависимости? Е. Г. вы могли бы преобразовать путем создания нового класса K, который использует, B и C внутри (вводится в класс K конструктором, затем с использованием композиции) - таким образом, количество параметров метода будет уменьшено на два.
промыть и повторить до тех пор, пока агрегирование не потеряет смысл и/или у вас есть разумное количество параметров.
Также см. соответствующее сообщение в блоге: "рефакторинг в Aggregate Services"
Я бы посоветовал переработать свое приложение. Если это невозможно, вы можете передать свой контейнер IoC в качестве параметра конструктора. Если вы не хотите связывать свой код с конкретной реализацией, вы всегда можете его абстрагировать. Код будет выглядеть примерно так.
public interface IAbstractContainer
{
T Resolve<T>();
}
public class ConcreteContainer: IAbstractContainer
{
private IContainer _container; // E.g. Autofac container
public ConcreteContainer(IContainer container)
{
_container = container;
{
public T Resolve<T>()
{
return _container.Resolve<T>();
}
}
public classA(IAbstractContainer container)
{
this.B = container.Resolve<ClassB>();
this.C = container.Resolve<ClassC>();
...
}
}
A ConcreteContainer
экземпляр вводится обычным способом.