IoC容器自动连接多个类型的实例(在.NET中)

时间:2017-11-07 17:29:46

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

我有一个使用依赖注入的应用程序,但目前不使用任何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容器实现的选择?

2 个答案:

答案 0 :(得分: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 { }

这是一些容器代码。你可以看到它变得复杂。如果您的依赖关系很小而且简单,那么它可能不值得。

以Windsor为例,您的设置可能如下所示。有多种方法可以做到,所以我会留给你决定它是否更简单或更可取。

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<IFoo>Foo[],然后Windsor将解析 all 实现并将它们传递给构造函数。您可以将其添加到容器设置中:

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

答案 1 :(得分:1)

使用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注入Foo Bar的原因。

<强>更新

  

如果我有两个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 });