如何使用 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)
});
假设(不一定使用 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)
});