Внедрить зависимость динамически на основе цепочки вызовов с использованием простого инжектора - PullRequest
0 голосов
/ 17 декабря 2018

В моем приложении я хочу построить следующие графы объектов, используя мой DI-контейнер, Simple Injector:

new Mode1(
    new CommonBuilder(
        new Grouping(
            new GroupingStrategy1())), // NOTE: Strategy 1
    new CustomBuilder());

new Mode2(
    new CommonBuilder(
        new Grouping(
            new GroupingStrategy2())), // NOTE: Strategy 2
    new CustomBuilder());

Следующие классы представляют графики выше:

public class Mode1 : IMode
{
    private readonly ICommonBuilder commonBuilder;
    private readonly ICustomBuilder customBuilder;

    public Mode1(ICommonBuilder commonBuilder, ICustomBuilder ICustomBuilder customBuilder)
    {
        this.commonBuilder = commonBuilder;
        this.customBuilder = customBuilder;
    }

    public void Run()
    {
        this.commonBuilder.Build();
        this.customBuilder.Build();

        //some code specific to Mode1
    }
}

public class Mode2 : IMode
{
    //same code as in Mode1

    public void Run()
    {
        this.commonBuilder.Build();
        this.customBuilder.Build();

        //some code specific to Mode2
    }
}

СCommonBuilder и Grouping:

public class CommonBuilder : ICommonBuilder
{
    private readonly IGrouping grouping;

    public CommonBuilder(IGrouping grouping)
    {
        this.grouping = grouping;
    }

    public void Build()
    {
        this.grouping.Group();
    }

}

public class Grouping : IGrouping
{
    //Grouping strategy should be binded based on mode it is running
    private readonly IGroupingStrategy groupingStrategy;

    public Grouping(IGroupingStrategy groupingStrategy)
    {
        this.groupingStrategy = groupingStrategy;
    }

    public void Group()
    {
        this.groupingStrategy.Execute();
    }
}

Я использую Simple Injector для DI в моем проекте.Как показано выше, у меня есть 2 режима кода, которые вызываются в соответствии с предпочтениями пользователя, у меня есть общий код для каждого режима (который я не хочу дублировать), я хочу связать свою стратегию группировки (I 'Мы используем 2 стратегии группирования, по одной для каждого режима) в моем общем коде, основанном на режиме выполнения.Я сталкивался с решением использовать фабрики и переключаться между привязками во время выполнения , но я бы не хотел использовать это решение, так как у меня один и тот же сценарий в нескольких местах в моем коде (я будув конечном итоге создание нескольких заводов).

Может кто-нибудь подсказать, как сделать переплет более чистым способом

1 Ответ

0 голосов
/ 17 декабря 2018

Вы можете использовать Контекстная инъекция .Однако, поскольку контекстно-зависимая инъекция, основанная на потребителе потребителя зависимости (или родителях их родителей), может привести к всевозможным осложнениям и незначительным ошибкам (особенно когда кешируется образ жизни, такой как Scoped и Singleton),API Simple Injector ограничивает вас на один уровень вверх.

Существует несколько способов обойти это кажущееся ограничение в Simple Injector.Первое, что вы должны сделать, это сделать шаг назад и посмотреть, сможете ли вы упростить свой дизайн, поскольку такие требования часто (но не всегда) возникают из-за неэффективности проектирования.Одной из таких проблем является нарушение принципа замены Лискова (LSP).С этой точки зрения, хорошо бы задать себе вопрос, сломается ли Mode1, когда ему вводят Grouping, который содержит стратегию для Mode2?Если ответ «да», вы, вероятно, нарушаете LSP, и вам следует прежде всего попытаться решить эту проблему в первую очередь.После исправления вы, скорее всего, также увидите, что ваши проблемы с конфигурацией исчезнут.

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

var container = new Container();

container.Collection.Append<IMode, Mode1>();
container.Collection.Append<IMode, Mode2>();

container.RegisterConditional(
    typeof(ICommonBuilder),
    c => typeof(CommonBuilder<>).MakeGenericType(c.Consumer.ImplementationType),
    Lifestyle.Transient,
    c => true);

container.RegisterConditional(
    typeof(IGrouping),
    c => typeof(Grouping<>).MakeGenericType(c.Consumer.ImplementationType),
    Lifestyle.Transient,
    c => true);

container.RegisterConditional<IGroupingStrategy, Strategy1>(
    c => typeof(Model1) == c.Consumer.ImplementationType
        .GetGenericArguments().Single() // Consumer.Consumer
        .GetGenericArguments().Single(); // Consumer.Consumer.Consumer

container.RegisterConditional<IGroupingStrategy, Strategy2>(
    c => typeof(Mode2)) == c.Consumer.ImplementationType
        .GetGenericArguments().Single()
        .GetGenericArguments().Single();

В этом примере вместо использования неуниверсального класса Grouping создается новый класс Grouping<T>, и то же самое делается для CommonBuilder<T>.Эти классы могут быть подклассом неуниверсальных классов Grouping и CommonBuilder, размещенных в корне композиции, поэтому вам не нужно изменять код приложения для этого:

class Grouping<T> : Grouping // inherit from base class
{
    public Grouping(IGroupingStrategy strategy) : base(strategy) { }
}

class CommonBuilder<T> : CommonBuilder // inherit from base class
{
    public CommonBuilder(IGrouping grouping) : base(grouping) { }
}

Используя этот общий CommonBuilder<T>, вы регистрируете, где T становится типом потребителя, в которого он вводится.Другими словами, Mode1 будет введен с CommonBuilder<Mode1>, а Mode2 получит CommonBuilder<Mode2>.Это идентично тому, что обычно при регистрации ILogger реализаций, как , показанное в документации .Однако из-за общей типизации CommonBuilder<Mode1> будет введено с Grouping<CommonBuilder<Mode1>>.

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

c => typeof(Mode2)) == c.Consumer.ImplementationType // = Grouping<CommonBuilder<Mode2>>
    .GetGenericArguments().Single() // = CommonBuilder<Mode2>
    .GetGenericArguments().Single(); // = Mode2

Другими словами, если мы можем изменить реализацию IGrouping таким образом, чтобы тип ее реализации (Grouping<T>) предоставлял информацию о своих потребителях (IMode реализации).Таким образом, условная регистрация IGroupingStrategy может использовать эту информацию о потребителе своего потребителя.

Здесь регистрация запрашивает тип реализации потребителя (который будет Grouping<Mode1> или Grouping<Mode2>) и будет захватывать одиночныйуниверсальный аргумент из этой реализации (который будет либо Mode1, либо Mode2).Другими словами, это позволяет нам получить потребителя.Это можно сопоставить с ожидаемым типом, возвращающим либо true, либо false.

. Хотя это кажется немного неуклюжим и сложным, преимущество этой модели состоит в том, что полный граф объектов известен Simple Injector,что позволяет анализировать и проверять граф объекта.Это также позволяет иметь автоматическую проводку.Другими словами, если реализации IGrouping или IGroupingStrategy имеют (другие) зависимости, Simple Injector автоматически внедрит их и проверит их правильность.Это также позволяет визуализировать граф объекта без потери какой-либо информации.Например, это график, который будет отображать Simple Injector, если вы наведите на него курсор мыши в отладчике Visual Studio:

Mode1(
    CommonBuilder<Mode1>(
        Grouping<CommonBuilder<Mode1>>(
            Strategy1()))

Очевидный недостаток этого подхода заключается в том, что если CommonBuilder<T> или Grouping<T>зарегистрированный как синглтон, теперь будет один экземпляр для закрытого универсального типа.Это означает, что CommonBuilder<Mode1> будет отличаться от CommonBuilder<Mode2>.

В качестве альтернативы, вы также можете просто сделать условную регистрацию CommonBuilder следующим образом:

var container = new Container();

container.Collection.Append<IMode, Mode1>();
container.Collection.Append<IMode, Mode2>();

container.RegisterConditional<ICommonBuilder>(
    Lifestyle.Transient.CreateRegistration(
        () => new CommonBuilder(new Grouping(new Strategy1())),
        container),
    c => c.Consumer.ImplementationType == typeof(Mode1));

container.RegisterConditional<ICommonBuilder>(
    Lifestyle.Transient.CreateRegistration(
        () => new CommonBuilder(new Grouping(new Strategy2())),
        container),
    c => c.Consumer.ImplementationType == typeof(Mode2));

Это немного проще, чем в предыдущем подходе, но отключает Auto-Wiring.В этом случае CommonBuilder и его зависимости соединяются вручную.Когда граф объектов прост (не содержит много зависимостей), этот метод может быть достаточно хорошим.Однако когда зависимости добавляются либо к CommonBuilder, Grouping, либо к стратегиям, это может привести к значительному обслуживанию и может скрывать ошибки, поскольку Simple Injector не сможет проверить график зависимостей от вашего имени.

Пожалуйста,Имейте в виду следующее утверждение в документации о методах RegisterConditional:

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

...