可以说(不一定是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,我可以在组成根目录而不是在类中进行更改:
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) {
...
}
我可以这样做吗?还是针对这种情况的更好的选择?如果是这样,怎么办?谢谢。
答案 0 :(得分:1)
这里有几个选项。例如,您可以使用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"));
您还可以按照自己的提议,用自己的自定义属性标记构造函数参数(尽管IMO这是最不吸引人的方法,因为您正在使用与该代码无关的属性来污染应用程序代码):< / p>
// 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>()));
此方法很简单,尽管它阻止了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)
});