【问题标题】:IoC container auto-wiring with multiple instances of a type (in .NET)具有多个类型实例的 IoC 容器自动装配(在 .NET 中)
【发布时间】:2017-11-07 17:29:45
【问题描述】:

我有一个使用依赖注入但当前不使用任何 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 容器实现的选择紧密耦合?

【问题讨论】:

  • 我相信Unity IoC 容器将与您的示例代码兼容,但我无法想象一个可以简化的 IoC 容器> 它。
  • 这并不是说如果您有其他依赖项的具体需求比您在问题中包含的类型更少,它不会简化您的整个项目。
  • 请阅读thisthis。并非所有应用程序都能从使用 DI 容器中受益。

标签: c# .net dependency-injection inversion-of-control ioc-container


【解决方案1】:

您最终会得到与 IOC 容器耦合的设置代码,但这没关系。关键是设置总是发生在应用程序的组合根中。或者换句话说,依赖关系是在应用程序启动时在使用类之前指定的。类本身将在不知道容器或依赖项是如何创建的情况下运行。

假设这些是您的 Foo 类和接口:

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 注册。每个都返回相同类型的对象 (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 的类,您必须通过名称指定您获得的版本,否则您只会获得“A”,因为这被指定为后备。

如您所见,这很快就会变得混乱和复杂。如果可能的话,另一种方法是使用抽象工厂在运行时选择一个实现,而不是为每个依赖项组合单独注册IFooHere's some more explanation and an example..

如果您使用的是 Windsor(我确信其他容器也有类似的行为),您可以有一个采用 IEnumerable&lt;IFoo&gt;Foo[] 的构造函数,然后 Windsor 将解析所有实现并将它们传递给构造函数。您可以将此添加到容器设置中:

container.Kernel.Resolver.AddSubResolver(new ListResolver(container.Kernel, true));

【讨论】:

  • 如果我看这个并将其与顶部的代码进行比较,我会说我并没有真正出售它。这些东西可能很棒且有用,但它是管理复杂性的工具。它并没有解决复杂性,它只是将它移动到其他地方,以便在你不需要处理它时让它远离你的头发。但如果管理复杂性实际上增加了复杂性,那么它的价值就值得怀疑了。
  • 注意:只有一个 FooConfig 类,它有两个实例。没有单独的 FooConfigA 和 FooConfigB 类。另外,重要的是,Foo除了FooConfig之外还有其他依赖,Bar和Baz除了Foo之外还有其他依赖。
  • 我不确定我是否正确地遵循了这一点,所以这一切都是笼统的,这是一件好事。如果没有注册类型,您只需要指定依赖项的值。因此,如果您的容器已经注册了其他类型的组件,那么它将自动解决这些问题。这是使 DI 容器有用的重要部分。有一些学习曲线,但如果你一直使用它,它会让生活更轻松。
  • 使用 Castle Windsor 确实最终增加了我设置中的代码行数,但现在它读起来更清晰,而且感觉更容易维护。我唯一真正的抱怨是,当一个类型具有多个相同类型的依赖项时,您必须使用字符串来指定参数名称,这意味着如果有人更改参数名称而不更改依赖项注册,则会出现运行时错误.感谢您的帮助!
【解决方案2】:

使用 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 不允许通过其直接父级“查找”调用图,因为这可能导致不正确的结果。这就是为什么我们不能使用自动连线将FooConfigB 注入BarFoo

更新

如果我有两个 Bar 实例,其中一个依赖 Foo 和 FooConfigA,另一个依赖 Foo 和 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 });

【讨论】:

  • 如果我有两个 Bar 实例,其中一个依赖 Foo 和 FooConfigA,另一个依赖 Foo 和 FooConfigB 怎么办?我可以使用 Castle Windsor 将其巧妙地连接起来,但我似乎无法找到一种方法让它与 SimpleInjector 或任何其他轻量级容器一起工作。
猜你喜欢
  • 1970-01-01
  • 2018-06-24
  • 1970-01-01
  • 2011-09-17
  • 1970-01-01
  • 2019-01-16
  • 2016-09-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多