c# контейнер - Как избежать безумства конструктора конструкции зависимостей?





пример injection (9)


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

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

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

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

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

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

public MyClass(Container con)

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




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

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

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

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




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

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

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

Container.GetSevice<MyClass>(someObject1, someObject2)

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

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

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

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

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




Это подход, который я использую

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




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

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

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

Рекомендации по IoC в сложном сервисном слое




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




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

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




Проблема:

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

2) Если класс наследуется (Ex: 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 , эта информация накапливается в A.Dependency
  • Подпись конструктора становится простой

Почему бы и нет

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

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




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

Это принцип разработки, в котором поток управления «принимается» из общей библиотеки или кода многократного использования.

Чтобы понять это лучше, давайте посмотрим, как мы использовали код в наши предыдущие дни кодирования. В процедурных / традиционных языках бизнес-логика обычно контролирует поток приложения и «вызывает» общий или многоразовый код / ​​функции. Например, в простом консольном приложении мой поток управления контролируется инструкциями моей программы, которые могут включать вызовы некоторых общих функций многократного использования.

print ("Please enter your name:");
scan (&name);
print ("Please enter your DOB:");
scan (&dob);

//More print and scan statements
<Do Something Interesting>

//Call a Library function to find the age (common code)
print Age

В Contrast, с IoC, Frameworks являются многоразовым кодом, который «вызывает» бизнес-логику.

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

Хотя код рамки не знает о моей бизнес-логике, он все равно будет знать, как вызвать мой код. Это достигается с помощью событий / делегатов, обратных вызовов и т. Д. Здесь управление потоком «Перевернуто».

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

Dependency Injection - это шаблон проектирования, который реализует принцип IoC для разрешения зависимостей объектов.

В более простых словах, когда вы пытаетесь написать код, вы будете создавать и использовать разные классы. Один класс (класс A) может использовать другие классы (класс B и / или D). Таким образом, классы B и D являются зависимостями класса A.

Простой аналогией будет класс Car. Автомобиль может зависеть от других классов, таких как Двигатель, Шины и многое другое.

Injection Dependency предполагает, что вместо зависимых классов (Class Car здесь), создающих его зависимости (Class Engine и класс Tire), классу следует вводить конкретный пример зависимости.

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

Class TextEditor
{

    //Lot of rocket science to create the Editor goes here

    EnglishSpellChecker objSpellCheck;
    String text;

    public void TextEditor()

    {   

        objSpellCheck = new EnglishSpellChecker();

    }

    public ArrayList <typos> CheckSpellings()
    {

        //return Typos;

    }

}

На первый взгляд все выглядит розовым. Пользователь напишет некоторый текст. Разработчик захватит текст и вызовет функцию CheckSpellings и найдет список Typos, который он покажет пользователю.

Кажется, что все работает отлично до одного прекрасного дня, когда один пользователь начинает писать французский в редакторе.

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

Здесь мы создали тесно связанный код с «английским» SpellChecker, тесно связанным с нашим классом TextEditor, что означает, что наш класс TextEditor зависит от EnglishSpellChecker, или, другими словами, EnglishSpellCheker является зависимостью от TextEditor. Нам нужно удалить эту зависимость. Кроме того, нашему текстовому редактору нужен способ удержания конкретной ссылки любой проверки орфографии на основе усмотрения разработчика во время выполнения.

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

interface ISpellChecker
{

    Arraylist<typos> CheckSpelling(string Text);

}

Class EnglishSpellChecker : ISpellChecker

{

    public override Arraylist<typos> CheckSpelling(string Text)

    {

        //All Magic goes here.

    }

}



Class FrenchSpellChecker : ISpellChecker

{

    public override Arraylist<typos> CheckSpelling(string Text)

    {

        //All Magic goes here.

    }

}

В нашем примере класс TextEditor должен получить конкретный экземпляр типа ISpellChecker.

Теперь зависимость может быть введена в Constructor, Public Property или метод.

Давайте попробуем изменить наш класс с помощью Constructor DI. Измененный класс TextEditor будет выглядеть примерно так:

Class TextEditor

{

    ISpellChecker objSpellChecker;

    string Text;



    public void TextEditor(ISpellChecker objSC)

    {

        objSpellChecker = objSC;

    }



    public ArrayList <typos> CheckSpellings()

    {

        return objSpellChecker.CheckSpelling();

    }

}

Чтобы вызывающий код при создании текстового редактора мог вводить соответствующий тип SpellChecker в экземпляр TextEditor.

Вы можете прочитать полную статью here





c# java dependency-injection inversion-of-control ioc-container