IoC 容器自动装配一个类型的多个实例(在 .NET 中)
IoC container auto-wiring with multiple instances of a type (in .NET)
我有一个应用程序使用依赖注入,但目前没有使用任何 IoC 容器。我目前在我的应用程序设置代码中有如下内容:
ISimpleDependency ez = new SimpleDependency();
ISomeOtherDependency otherDep = new SomeOtherDependency();
FooConfig fooConfigA = Settings.Default.FooConfigA;
FooConfig fooConfigB = Settings.Default.FooConfigB;
IFoo fooA = new Foo(fooConfigA, ez);
IFoo fooB = new Foo(fooConfigB, ez);
Bar bar = new Bar(fooA, otherDep);
Baz baz = new Baz(fooB, ez, otherDep);
Qux qux = new Qux(fooA, fooB); //params IFoo[] constructor to which we want to pass every Foo
为了降低我的应用设置代码的复杂度并提高可维护性,我想引入一个 IoC 容器。有什么方法可以在这里使用 IoC 容器来自动连接所有内容,而无需将 Foo/Bar/Baz/Qux 类 与 IoC 容器实现的选择紧密耦合?
您最终会得到耦合到 IOC 容器的设置代码,但这没关系。关键是设置始终发生在应用程序的组合根中。或者换句话说,依赖关系是在应用程序启动时在使用 classes 之前指定的。 classes 本身将在不知道容器或如何创建依赖项的情况下运行。
假设这些是您的 Foo
classes 和接口:
public interface IFoo { }
public interface IFooConfig { }
public class Foo : IFoo
{
private readonly IFooConfig _config;
public Foo(IFooConfig config)
{
_config = config;
}
}
public class FooConfigA : IFooConfig { }
public class FooConfigB : IFooConfig { }
这是一些容器代码。如您所见,它变得复杂了。如果您的依赖关系小而简单,则可能不值得。
以温莎为例,您的设置可能如下所示。有不止一种方法可以做到这一点,所以我会把它留给你来决定它是更简单还是更可取。
container.Register(
Component.For<IFooConfig, FooConfigA>().Named("FooConfigA"),
Component.For<IFooConfig, FooConfigB>().Named("FooConfigB"),
Component.For<IFoo, Foo>()
.DependsOn(Dependency.OnComponent<IFooConfig, FooConfigA>()).Named("FooA")
.IsFallback(),
Component.For<IFoo, Foo>()
.DependsOn(Dependency.OnComponent<IFooConfig, FooConfigB>()).Named("FooB"));
现在您有两个不同名称的 IFoo
注册。每个 returns 相同类型的对象 (Foo
) 但每个实例都有不同的 IFooConfiguration
.
实现
或者,如果您正在使用来自 Settings.Default
的 IFooConfig 实例,您可以这样做:
Component.For<IFoo, Foo>()
.DependsOn(Dependency.OnValue("config", Default.Settings.FooConfigA))
.Named("FooA")
.IsFallback(),
Component.For<IFoo, Foo>()
.DependsOn(Dependency.OnValue("config", Default.Settings.FooConfigB))
.Named("FooB"),
现在,对于依赖于 IFoo
的每个 class,您必须通过名称指定您获得的版本,或者您将获得 "A",因为它被指定为回退。
如您所见,这很快就会变得混乱和复杂。如果可能,另一种方法是使用抽象工厂在运行时选择实现,而不是为每个依赖项组合单独注册 IFoo
。 Here's some more explanation and an example..
如果您使用的是 Windsor(我相信其他容器也有类似的行为),您可以有一个构造函数,该构造函数采用 IEnumerable<IFoo>
或 Foo[]
,然后 Windsor 将解析 all 实现并将它们传递给构造函数。您可以将其添加到容器设置中:
container.Kernel.Resolver.AddSubResolver(new ListResolver(container.Kernel, true));
使用 Simple Injector 时,以下注册等同于您当前的 Pure DI 方法:
var container = new Container();
container.Register<ISimpleDependency, SimpleDependency>();
container.Register<ISomeOtherDependency, SomeOtherDependency>();
container.Register<Bar>();
container.Register<Baz>();
container.Register<Qux>();
var fooAReg = Lifestyle.Transient.CreateRegistration<IFoo>(
() => new Foo(Settings.Default.FooConfigA, container.GetInstance<ISimpleDependency>()),
container);
var fooBReg = Lifestyle.Transient.CreateRegistration<IFoo>(
() => new Foo(Settings.Default.FooConfigB, container.GetInstance<ISimpleDependency>()),
container);
// The registrations for IFoo are conditional. We use fooA for Bar and fooB for Baz.
container.RegisterConditional(typeof(IFoo), fooAReg,
c => c.Consumer.ImplementationType == typeof(Bar));
container.RegisterConditional(typeof(IFoo), fooBReg,
c => c.Consumer.ImplementationType == typeof(Baz));
container.RegisterCollection<IFoo>(new[] { fooAReg, fooBReg });
请注意,此示例并未对所有注册使用自动装配; Foo
注册是手动连接的。这是因为(作为一项安全措施)Simple Injector 不允许 'looking up' 调用图超过其直接父级,因为这可能导致不正确的结果。这就是为什么我们不能使用自动装配将 FooConfigB
注入 Bar
的 Foo
。
更新:
What if I have two instances of Bar, one of which depends on the Foo with FooConfigA, and the other of which depends on the Foo with FooConfigB
var fooAProd = Lifestyle.Transient.CreateProducer<IFoo>(
() => new Foo(Settings.Default.FooConfigA, container.GetInstance<ISimpleDependency>()),
container);
var bar1Reg = Lifestyle.Transient.CreateRegistration<Bar>(() => new Bar(
fooAProd.GetInstance(),
container.GetInstance<IOtherDep1>()));
var fooBProd = Lifestyle.Transient.CreateProducer<IFoo>(
() => new Foo(Settings.Default.FooConfigB, container.GetInstance<ISimpleDependency>()),
container);
var bar2Reg = Lifestyle.Transient.CreateRegistration<Bar>(() => new Bar(
fooBProd.GetInstance(),
container.GetInstance<IOtherDep1>()));
// The registrations for IFoo are conditional. We use fooA for Bar and fooB for Baz.
container.RegisterConditional(typeof(IFoo), fooBProd.Registration,
c => c.Consumer.ImplementationType == typeof(Baz));
container.RegisterCollection<IFoo>(new[] { fooAReg, fooBReg });
我有一个应用程序使用依赖注入,但目前没有使用任何 IoC 容器。我目前在我的应用程序设置代码中有如下内容:
ISimpleDependency ez = new SimpleDependency();
ISomeOtherDependency otherDep = new SomeOtherDependency();
FooConfig fooConfigA = Settings.Default.FooConfigA;
FooConfig fooConfigB = Settings.Default.FooConfigB;
IFoo fooA = new Foo(fooConfigA, ez);
IFoo fooB = new Foo(fooConfigB, ez);
Bar bar = new Bar(fooA, otherDep);
Baz baz = new Baz(fooB, ez, otherDep);
Qux qux = new Qux(fooA, fooB); //params IFoo[] constructor to which we want to pass every Foo
为了降低我的应用设置代码的复杂度并提高可维护性,我想引入一个 IoC 容器。有什么方法可以在这里使用 IoC 容器来自动连接所有内容,而无需将 Foo/Bar/Baz/Qux 类 与 IoC 容器实现的选择紧密耦合?
您最终会得到耦合到 IOC 容器的设置代码,但这没关系。关键是设置始终发生在应用程序的组合根中。或者换句话说,依赖关系是在应用程序启动时在使用 classes 之前指定的。 classes 本身将在不知道容器或如何创建依赖项的情况下运行。
假设这些是您的 Foo
classes 和接口:
public interface IFoo { }
public interface IFooConfig { }
public class Foo : IFoo
{
private readonly IFooConfig _config;
public Foo(IFooConfig config)
{
_config = config;
}
}
public class FooConfigA : IFooConfig { }
public class FooConfigB : IFooConfig { }
这是一些容器代码。如您所见,它变得复杂了。如果您的依赖关系小而简单,则可能不值得。
以温莎为例,您的设置可能如下所示。有不止一种方法可以做到这一点,所以我会把它留给你来决定它是更简单还是更可取。
container.Register(
Component.For<IFooConfig, FooConfigA>().Named("FooConfigA"),
Component.For<IFooConfig, FooConfigB>().Named("FooConfigB"),
Component.For<IFoo, Foo>()
.DependsOn(Dependency.OnComponent<IFooConfig, FooConfigA>()).Named("FooA")
.IsFallback(),
Component.For<IFoo, Foo>()
.DependsOn(Dependency.OnComponent<IFooConfig, FooConfigB>()).Named("FooB"));
现在您有两个不同名称的 IFoo
注册。每个 returns 相同类型的对象 (Foo
) 但每个实例都有不同的 IFooConfiguration
.
或者,如果您正在使用来自 Settings.Default
的 IFooConfig 实例,您可以这样做:
Component.For<IFoo, Foo>()
.DependsOn(Dependency.OnValue("config", Default.Settings.FooConfigA))
.Named("FooA")
.IsFallback(),
Component.For<IFoo, Foo>()
.DependsOn(Dependency.OnValue("config", Default.Settings.FooConfigB))
.Named("FooB"),
现在,对于依赖于 IFoo
的每个 class,您必须通过名称指定您获得的版本,或者您将获得 "A",因为它被指定为回退。
如您所见,这很快就会变得混乱和复杂。如果可能,另一种方法是使用抽象工厂在运行时选择实现,而不是为每个依赖项组合单独注册 IFoo
。 Here's some more explanation and an example..
如果您使用的是 Windsor(我相信其他容器也有类似的行为),您可以有一个构造函数,该构造函数采用 IEnumerable<IFoo>
或 Foo[]
,然后 Windsor 将解析 all 实现并将它们传递给构造函数。您可以将其添加到容器设置中:
container.Kernel.Resolver.AddSubResolver(new ListResolver(container.Kernel, true));
使用 Simple Injector 时,以下注册等同于您当前的 Pure DI 方法:
var container = new Container();
container.Register<ISimpleDependency, SimpleDependency>();
container.Register<ISomeOtherDependency, SomeOtherDependency>();
container.Register<Bar>();
container.Register<Baz>();
container.Register<Qux>();
var fooAReg = Lifestyle.Transient.CreateRegistration<IFoo>(
() => new Foo(Settings.Default.FooConfigA, container.GetInstance<ISimpleDependency>()),
container);
var fooBReg = Lifestyle.Transient.CreateRegistration<IFoo>(
() => new Foo(Settings.Default.FooConfigB, container.GetInstance<ISimpleDependency>()),
container);
// The registrations for IFoo are conditional. We use fooA for Bar and fooB for Baz.
container.RegisterConditional(typeof(IFoo), fooAReg,
c => c.Consumer.ImplementationType == typeof(Bar));
container.RegisterConditional(typeof(IFoo), fooBReg,
c => c.Consumer.ImplementationType == typeof(Baz));
container.RegisterCollection<IFoo>(new[] { fooAReg, fooBReg });
请注意,此示例并未对所有注册使用自动装配; Foo
注册是手动连接的。这是因为(作为一项安全措施)Simple Injector 不允许 'looking up' 调用图超过其直接父级,因为这可能导致不正确的结果。这就是为什么我们不能使用自动装配将 FooConfigB
注入 Bar
的 Foo
。
更新:
What if I have two instances of Bar, one of which depends on the Foo with FooConfigA, and the other of which depends on the Foo with FooConfigB
var fooAProd = Lifestyle.Transient.CreateProducer<IFoo>(
() => new Foo(Settings.Default.FooConfigA, container.GetInstance<ISimpleDependency>()),
container);
var bar1Reg = Lifestyle.Transient.CreateRegistration<Bar>(() => new Bar(
fooAProd.GetInstance(),
container.GetInstance<IOtherDep1>()));
var fooBProd = Lifestyle.Transient.CreateProducer<IFoo>(
() => new Foo(Settings.Default.FooConfigB, container.GetInstance<ISimpleDependency>()),
container);
var bar2Reg = Lifestyle.Transient.CreateRegistration<Bar>(() => new Bar(
fooBProd.GetInstance(),
container.GetInstance<IOtherDep1>()));
// The registrations for IFoo are conditional. We use fooA for Bar and fooB for Baz.
container.RegisterConditional(typeof(IFoo), fooBProd.Registration,
c => c.Consumer.ImplementationType == typeof(Baz));
container.RegisterCollection<IFoo>(new[] { fooAReg, fooBReg });