Как использовать несколько типов с одним и тем же интерфейсом в разных сценариях с SimpleInjector? - PullRequest
0 голосов
/ 26 декабря 2018

Допустим, (не обязательно с DI) у меня есть интерфейс, чтобы что-то делать, и два разных класса, реализующих его (делающие разные «что-то»):

public interface ISomethingDoer {
    void DoSomething();
}

public class DoerOfSomethingExciting : IDoSomething { ... }
public class DoerOfSomethingBoring : IDoSomething { ... }

И у меня где-то есть метод, который используетISomethingDoers обоих типов:

public class DoerOfVariousThings {
    ...

    public DoerOfVariousThings(ISomethingDoer excitingDoer, ISomethingDoer boringDoer) 
    {
        ...
    }

    public void DoVariousThings() {
        this.excitingDoer.DoSomething();
        this.boringDoer.DoSomething();
    }
}

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

public void DoVariousThings() {
    this.excitingDoer.DoSomething();
    this.boringDoer.DoSomething();
}

Я мог бы просто поменять эти две строки,и я сделал.Но то, что я хотел бы больше, это что-то похожее на следующее:

public class DoerOfVariousThings {
    ...

    public DoerOfVariousThings(ISomethingDoer firstDoer, ISomethingDoer secondDoer) 
    {
        ...
    }

    public void DoVariousThings() {
        this.firstDoer.DoSomething();
        this.secondDoer.DoSomething();
    }
}

Таким образом, с простым, не контейнерным, DI "по-своему", я мог бы внести изменения в составroot, а не в классе:

public void BuildRoot() {
    // The change can be made by slightly modifying the following two lines
    ISomethingDoer firstDoer = new DoerOfSomethingExciting();
    ISomethingDoer secondDoer = new DoerOfSomethingBoring();

    var variousDoer = new DoerOfVariousThings(firstDoer, secondDoer);

    ...
}

Но как мне точно поддерживать такие же вещи с помощью DI на основе контейнеров?В частности, я пытаюсь использовать SimpleInjector, но более общие ответы или ответы для разных структур также могут быть полезны.

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

На самом деле, я думаю, мне бы хотелось что-то вроде следующего (воображаемый синтаксис):

using (var container = ...) {
    container.Register<ISomethingDoer, DoerOfSomethingExciting>("first");
    container.Register<ISomethingDoer, DoerOfSomethingBoring>("second");

    ...
}

...

[DIParamMap("first", firstDoer)]
[DIParamMap("second", secondDoer)]
public DoerOfVariousThings(ISomethingDoer firstDoer, ISomethingDoer secondDoer) {
    ...
}

МожетЯ делаю что-то подобное?Или что-то лучше для такого сценария?Если да, то как?Спасибо.

1 Ответ

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

Здесь есть несколько вариантов.Например, вы можете использовать контекстное внедрение , зарегистрировать DoerOfVariousThings с использованием делегата или ввести последовательность ISomethingDoer экземпляров в DoerOfVariousThings вместо использования отдельных аргументов конструктора.

При применении контекстного внедрения есть несколько способов добиться этого.Например, вы можете основать внедренную зависимость на имени аргумента конструктора:

container.RegisterConditional<ISomethingDoer, DoerOfSomethingExciting>(
    c => c.Consumer.Target.Name.StartsWith("first"));
container.RegisterConditional<ISomethingDoer, DoerOfSomethingBoring>(
    c => c.Consumer.Target.Name.StartsWith("second"));

Вы также можете пометить аргументы конструктора своими собственными пользовательскими атрибутами, так же, как вы предлагаете (хотя IMO этонаименее привлекательный подход, так как вы пишете свой код приложения с атрибутами, которые не имеют отношения к этому коду):

// ctor with custom attributes
public DoerOfVariousThings(
    [Name("first")]ISomethingDoer firstDoer,
    [Name("second")]ISomethingDoer secondDoer)

container.RegisterConditional<ISomethingDoer, DoerOfSomethingExciting>(
    c => c.Consumer.Target.GetCustomAttribute<NameAttribute>().Name == "first");
container.RegisterConditional<ISomethingDoer, DoerOfSomethingBoring>(
    c => c.Consumer.Target.GetCustomAttribute<NameAttribute>().Name == "second");

Другой вариант - зарегистрировать делегата для DoerOfVariousThings.Например:

container.Register<DoerOfVariousThings>(() => new DoerOfVariousThings(
    firstDoer: container.GetInstance<DoerOfSomethingExciting>(),
    secondDoer: container.GetInstance<DoerOfSomethingBoring>()));

Этот подход прост, хотя он не позволяет Simple Injector анализировать ваши графы объектов .Поэтому рекомендуется использовать RegisterConditional.

Другой вариант - изменить конструктор DoerOfVariousThings на следующий:

// ctor with a collection of doers
public DoerOfVariousThings(IEnumerable<ISomethingDoer> doers)

Это позволяет регистрировать исполнителей следующим образом:

container.Collection.Register<ISomethingDoer>(typeof(DoerOfSomethingBoring).Assembly);

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

container.Collection.Append<ISomethingDoer, DoerOfSomethingExciting>();
container.Collection.Append<ISomethingDoer, DoerOfSomethingBoring>();

Это добавляет обе реализации к коллекции ISomethingDoer экземпляров, которые будут внедрены в DoerOfVariousThings.Simple Injector гарантирует, что компоненты вводятся в порядке регистрации (что в данном случае означает, что DoerOfSomethingExciting - это элемент 0, а DoerOfSomethingBoring - это элемент 1).

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

container.Collection.Register<ISomethingDoer>(new[]
{
    typeof(DoerOfSomethingExciting),
    typeof(DoerOfSomethingBoring)
});
...