Как избежать сумасшествия конструктора Dependency Injection? - PullRequest
279 голосов
/ 10 марта 2010

Я считаю, что мои конструкторы начинают выглядеть так:

public MyClass(Container con, SomeClass1 obj1, SomeClass2, obj2.... )

с постоянно увеличивающимся списком параметров. Поскольку «Контейнер» является моим контейнером внедрения зависимостей, почему я не могу просто сделать это:

public MyClass(Container con)

для каждого класса? Каковы недостатки? Если я делаю это, я чувствую, что использую прославленную статику. Пожалуйста, поделитесь своими мыслями о безумии IoC и Dependency Injection.

Ответы [ 9 ]

379 голосов
/ 10 марта 2010

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

Одним из замечательных преимуществ использования Constructor Injection является то, что он делает нарушения Принципа единой ответственности наглядно очевидными.

Когда это произойдет, пришло время рефакторинг для Facade Services . Короче говоря, создайте новый, более грубый интерфейс, который скрывает взаимодействие между некоторыми или всеми мелкозернистыми зависимостями, которые вам нужны в настоящее время.

58 голосов
/ 10 марта 2010

Я не думаю, что ваши конструкторы классов должны иметь ссылку на ваш период контейнера IOC. Это представляет ненужную зависимость между вашим классом и контейнером (тип зависимости, которую IOC пытается избежать!).

24 голосов
/ 11 марта 2010

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

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

3 голосов
/ 19 октября 2015

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

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

Подробный код с пояснениями можно найти здесь

Лучшие практики для IoC на уровне комплексных услуг

2 голосов
/ 22 октября 2018

Внедрение контейнера - это ярлык, о котором вы в конечном итоге пожалеете.

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

Вот неполный список вещей, которые нужно посмотреть

Плохой дизайн домена (агрегирование корневых доменов…. И т. Д.)

Плохое разделение интересов (Состав службы, Команды, запросы) См. CQRS и Sourcing Event.

ИЛИ Mappers (будьте осторожны, эти вещи могут привести к неприятностям)

Просмотр моделей и других DTO (Никогда не используйте повторно и старайтесь свести их к минимуму !!!!)

1 голос
/ 12 сентября 2018

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

Исходный вопрос JP выглядит так, как будто он строит объекты, отправляя распознаватель, а затем группу классов, но мы предполагаем, что эти классы / объекты сами по себе являются сервисами, готовыми для внедрения. Что делать, если это не так?

JP, если вы хотите использовать DI , а желает славы смешивания инъекций с контекстными данными, ни один из этих шаблонов (или предполагаемых «анти-шаблонов») специально не решает эту проблему. На самом деле все сводится к использованию пакета, который поддержит вас в таких усилиях.

Container.GetSevice<MyClass>(someObject1, someObject2)

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

Но это должно быть сделано, потому что я должен иметь возможность создавать и регистрировать фабрику для MyClass'ов, и эта фабрика должна иметь возможность получать данные / ввод, которые не превращаются в «службу» только для Ради передачи данных. Если «анти-паттерн» связан с негативными последствиями, то форсирование существования искусственных типов сервисов для передачи данных / моделей, безусловно, негативно (наравне с вашим ощущением, что вы упаковываете свои классы в контейнер. Тот же инстинкт применим).

Существуют рамки, которые могут помочь, даже если они выглядят немного некрасиво. Например, Ninject:

Создание экземпляра с использованием Ninject с дополнительными параметрами в конструкторе

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

1 голос
/ 08 июня 2017

Проблема:

1) Конструктор с постоянно увеличивающимся списком параметров.

2) Если класс наследуется (например: RepositoryBase), то изменение конструктора подпись вызывает изменения в производных классах.

Решение 1

Передача IoC Container в конструктор

Почему

  • Больше не увеличивается список параметров
  • Подпись конструктора становится простой

Почему бы не

  • Делает ваш класс тесно связанным с контейнером IoC. (Это вызывает проблемы, когда 1. вы хотите использовать этот класс в других проектах, где вы используете другой контейнер IoC. 2. вы решили изменить контейнер IoC)
  • Делает ваш класс менее наглядным. (Вы не можете реально взглянуть на конструктор класса и сказать, что ему нужно для работы.)
  • Класс может получить доступ ко всем услугам.

Решение 2

Создайте класс, который группирует все службы и передайте его конструктору

 public abstract class EFRepositoryBase 
 {
    public class Dependency
    {
        public DbContext DbContext { get; }
        public IAuditFactory AuditFactory { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
        }
    }

    protected readonly DbContext DbContext;        
    protected readonly IJobariaAuditFactory auditFactory;

    protected EFRepositoryBase(Dependency dependency)
    {
        DbContext = dependency.DbContext;
        auditFactory= dependency.JobariaAuditFactory;
    }
  }

Производный класс

  public class ApplicationEfRepository : EFRepositoryBase      
  {
     public new class Dependency : EFRepositoryBase.Dependency
     {
         public IConcreteDependency ConcreteDependency { get; }

         public Dependency(
            DbContext dbContext,
            IAuditFactory auditFactory,
            IConcreteDependency concreteDependency)
        {
            DbContext = dbContext;
            AuditFactory = auditFactory;
            ConcreteDependency = concreteDependency;
        }
     }

      IConcreteDependency _concreteDependency;

      public ApplicationEfRepository(
          Dependency dependency)
          : base(dependency)
      { 
        _concreteDependency = dependency.ConcreteDependency;
      }
   }

Почему

  • Добавление новой зависимости к классу не влияет на производные классы
  • Класс агностик IoC Контейнер
  • Класс является описательным (в аспекте его зависимостей). По соглашению, если вы хотите знать, от какого класса A зависит, эта информация накапливается в A.Dependency
  • Подпись конструктора становится простой

Почему бы не

  • нужно создать дополнительный класс
  • Услуга регистрации становится сложной (Вам необходимо регистрироваться каждые X.Dependency отдельно)
  • Концептуально то же, что и прохождение IoC Container
  • ..

Решение 2 является просто необработанным, хотя, если против него есть веские аргументы, то приветствуется описательный комментарий

0 голосов
/ 20 августа 2017

Я использую этот подход

public class Hero
{

    [Inject]
    private IInventory Inventory { get; set; }

    [Inject]
    private IArmour Armour { get; set; }

    [Inject]
    protected IWeapon Weapon { get; set; }

    [Inject]
    private IAction Jump { get; set; }

    [Inject]
    private IInstanceProvider InstanceProvider { get; set; }


}

Вот грубый подход к выполнению инъекций и запуску конструктора после введения значений. Это полнофункциональная программа.

public class InjectAttribute : Attribute
{

}


public class TestClass
{
    [Inject]
    private SomeDependency sd { get; set; }

    public TestClass()
    {
        Console.WriteLine("ctor");
        Console.WriteLine(sd);
    }
}

public class SomeDependency
{

}


class Program
{
    static void Main(string[] args)
    {
        object tc = FormatterServices.GetUninitializedObject(typeof(TestClass));

        // Get all properties with inject tag
        List<PropertyInfo> pi = typeof(TestClass)
            .GetProperties(BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Public)
            .Where(info => info.GetCustomAttributes(typeof(InjectAttribute), false).Length > 0).ToList();

        // We now happen to know there's only one dependency so we take a shortcut just for the sake of this example and just set value to it without inspecting it
        pi[0].SetValue(tc, new SomeDependency(), null);


        // Find the right constructor and Invoke it. 
        ConstructorInfo ci = typeof(TestClass).GetConstructors()[0];
        ci.Invoke(tc, null);

    }
}

В настоящее время я работаю над хобби-проектом, который работает следующим образом. https://github.com/Jokine/ToolProject/tree/Core

0 голосов
/ 11 марта 2010

Какую инфраструктуру внедрения зависимостей вы используете? Вы пробовали вместо этого использовать инъекцию на основе сеттера?

Преимущество внедрения на основе конструктора состоит в том, что это выглядит естественным для программистов Java, которые не используют интегрированные среды DI. Вам нужно 5 вещей для инициализации класса, тогда у вас есть 5 аргументов для вашего конструктора. Недостатком является то, что вы заметили, это становится громоздким, когда у вас много зависимостей.

С помощью Spring вы можете вместо этого передавать требуемые значения с помощью сеттеров и использовать аннотации @required, чтобы обеспечить их внедрение. Недостатком является то, что вам нужно переместить код инициализации из конструктора в другой метод и вызвать Spring, который после внедрения всех зависимостей помечает его как @PostConstruct. Я не уверен насчет других фреймворков, но предполагаю, что они делают нечто подобное.

Оба способа работают, это вопрос предпочтения.

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