如何使用 SimpleInjector 在不同场景下使用具有相同接口的多种类型?

How do I use multiple types with the same interface in different scenarios with SimpleInjector?

假设(不一定使用 DI)我有一个接口来做某事,并且有两个不同的 classes 实现它(做不同的 "somethings"):

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,我可以在组合根而不是 class:

中进行更改
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 实现注册为具体的 classes 而不是接口实现,但这似乎不是最优的:如果我理解正确,DoerOfVariousThings 构造函数将不得不采用那些具体的 classes 而不是它们的接口,否则我将通过交换 DoerOfVariousThings 构造函数的参数顺序而不是通过更改 newing-up 来更改组合根填充这些参数的对象。

真的,我想我想要的更像下面的东西(想象的语法):

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) {
    ...
}

我可以这样做吗?或者更适合这种情况的东西?如果是这样,如何?谢谢。

这里有几个选项。例如,您可以使用 context-based injection、使用委托注册 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"));

你也可以用你自己的自定义属性标记构造函数参数,就像你提议的那样(尽管在我看来这是最没有吸引力的方法,因为你正在用与该代码无关的属性污染你的应用程序代码) :

// 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 analyzing your object graphs。因此,首选使用 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)
});