Внедрение зависимостей - что делать, если у вас много зависимостей? - PullRequest
13 голосов
/ 13 марта 2012

У меня есть класс А, который зависит от 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;
}

Я читал на Мартина ФаулераКнига о рефакторинге, что наличие метода с большим количеством параметров является запахом кода и не должно происходить.

Мой вопрос: это нормально, когда мы говорим о DI?Есть ли лучший способ внедрения зависимостей без нарушения правил Мартина Фаулера?

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

РЕДАКТИРОВАТЬ

Спасибо за все ваши ответы.Сейчас я попытаюсь продемонстрировать некоторые зависимости класса A:

1 - класс для доступа к БД
2 - другой класс для доступа к другой БД (да, мне нужно выполнить операции с двумя базами данных)
3 - класс для отправки уведомлений об ошибках по электронной почте
4 - класс для загрузки конфигураций
5 - класс, который будет действовать как таймер для некоторых операций (возможно, этого можно избежать)
6 -Класс с бизнес-логикой

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

EDIT

После некоторого рефакторинга теперь у меня 7 зависимостей (вместо 10).Но у меня есть 4 DAO объектов:

CustomerDAO
ProcessDAO
ProductsDAO
CatalogDAO

Правильно ли создать другой класс с именем MyProjectDAO и внедрить эти DAOS в него?Таким образом, у меня будет только один класс DAO, который объединяет все объекты DAO моего проекта.Я не думаю, что это хорошая идея, потому что она нарушает принцип единой ответственности.Я прав?

Ответы [ 4 ]

14 голосов
/ 13 марта 2012

По моему опыту:

  • Попробуйте спроектировать свой класс так, чтобы ему было меньше зависимостей. Если ему нужно так много, у него может быть слишком много обязанностей.
  • Если вы действительно убеждены в том, что ваш классовый дизайн подходит, подумайте, может ли иметь смысл объединять некоторые из этих зависимостей (например, через адаптер, который берет на себя ответственность за одну "большую" операцию, которая нужна вашему классу, делегируя до нескольких зависимостей). Затем вы можете зависеть от адаптера вместо «меньших» зависимостей.
  • Если любой другой бит действительно имеет смысл, просто проглотите запах множества параметров. Такое бывает иногда.
11 голосов
/ 13 марта 2012

Можете ли вы объяснить (себе), почему класс зависит от 10 других классов? Существуют ли переменные-члены, которые вы используете для связывания подмножества этих классов? Если это так, это означает, что этот класс должен быть разбит так, чтобы извлеченный класс зависел от подмножества, а переменные, связывающие такое состояние, попадали в извлеченный класс. С 10 зависимостями, возможно, этот класс просто стал слишком большим и ему все равно нужно разбить его внутренние компоненты.

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

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

Редактировать: Теперь, когда я подумал об этом, я не уверен, что существенные сложности вашего алгоритма или протокола не могут быть абстрагированы (хотя это может иметь место). В зависимости от конкретной проблемы, сходства в манипуляциях с этими зависимыми классами могут быть лучше решены с помощью шаблона «Стратегия» или шаблона «Наблюдатель» (прослушиватели событий). Возможно, вам придется обернуть эти классы в классы, которые адаптируют их к интерфейсам, немного отличным от того, что они в настоящее время предоставляют. Вы должны оценить компромисс между тем, чтобы код в этом классе монстров стал более читабельным (yay) за счет еще до 10 классов в вашем проекте (boo).

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

Редактировать 2 (на основании вашего редактирования):

Моя первая мысль: "Что, нет зависимости от журналирования?" :)

Даже зная, каковы зависимости, сложно дать полезный совет.

Во-первых: каковы обязанности каждого? Почему этот класс зависит от кода контроллера (бизнес-логики) и от кода модели (два разных класса доступа к базе данных с классами DAO)?

В зависимости как от DAO, так и от классов доступа к БД, запах кода. Какова цель DAO? Какова цель классов БД? Вы пытаетесь работать на нескольких уровнях абстракции?

Один из принципов ОО состоит в том, что данные и поведение объединяются в мелочи, называемые классами. Вы нарушили это, когда создали этот класс бизнес-логики, отличный от объектов, которыми он манипулирует, отличных от DAO, отличного от этого класса? Связанный: Сделайте краткий переход на SOLID .

Второй: класс для загрузки конфигураций. Плохо пахнет. Внедрение зависимостей помогает вам определить зависимости и поменять их местами. Ваш класс монстров, который зависит от определенных параметров. Эти параметры сгруппированы в этот класс конфигурации, потому что ...? Как называется этот класс конфигурации? Это DBparameters? если это так, он принадлежит объекту (ам) БД, а не этому классу. Является ли это универсальным, как конфигурации? Если это так, то у вас есть мини-инжектор зависимостей (да, возможно, он вводит только строковые или целочисленные значения вместо составных данных, таких как классы, но почему?). Неудобно.

В-третьих: Самый важный урок, который я извлек из рефакторинга, заключался в том, что мой код высосан. Мало того, что мой код был отстой, но не было ни одного преобразования, чтобы остановить его. Лучшее, на что я мог надеяться, это заставить его сосать меньше. Как только я это сделаю, я смогу снова меньше сосать. И опять. Некоторые шаблоны проектирования плохие, но они существуют для того, чтобы позволить вашему отстойному коду перейти на менее отстойный. Таким образом, вы берете свои глобалы и делаете их одиночными. Тогда вы устраняете свои одиночки. Не расстраивайтесь, потому что вы просто изменили рефакторинг и обнаружили, что ваш код по-прежнему отстой. Это отстой меньше. Таким образом, ваш объект загрузки конфигурации может пахнуть, но вы можете решить, что это не самая неприятная часть вашего кода. На самом деле, вы можете обнаружить, что усилия по «исправлению» этого не стоят.

3 голосов
/ 13 марта 2012

Да - метод, принимающий столько параметров, должен рассматриваться как запах кода. Действительно ли этот метод делает только одно и только одно?

Если это все еще верно, вы можете все же уменьшить количество зависимостей, посмотрев на взаимосвязи между зависимостями. Являются ли какие-либо из них тесно связанными, могут ли они быть объединены в совокупные зависимости? Например. Вы можете выполнить рефакторинг, создав новый класс K, который использует A, B и C для внутреннего использования (вводится в класс K конструктором, а затем с помощью композиции), поэтому число параметров метода будет сокращено на два.

Промывайте и повторяйте, пока агрегирование больше не будет иметь смысла и / или у вас будет достаточное количество параметров.

Также см. Сообщение в блоге: «Рефакторинг для агрегированных сервисов»

0 голосов
/ 13 марта 2012

Я бы также посоветовал изменить дизайн вашего приложения. Если это невозможно, вы можете передать свой контейнер 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 экземпляр вводится обычным способом.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...