配置Unity以解析采用装饰依赖项的类型,该依赖项的参数随注入的类型而变化

时间:2012-01-25 14:28:07

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

这是一个相当直接的装饰器模式场景,其复杂性在于,装饰类型具有一个构造函数参数,该参数取决于它被注入的类型。

我有这样的界面:

interface IThing
{
    void Do();
}

这样的实现:

class RealThing : IThing
{
    public RealThing(string configuration)
    {
        ... implementation ...
    }

    public void Do()
    {
        ... implementation ...
    }
}

这样的装饰者:

class DecoratingThing : IThing
{
    IThing _innerThing;

    public DecoratingThing(IThing thing)
    {
        _innerThing = thing;    
    }

    public void Do()
    {
        _innerThing.Do();
    }
}

最后,我的某些类型需要IThing,称为Depender1Depender2等。

class DependerX()
{
    public DependerX(IThing thing)
    {
        ... implementation ...
    }
}

我想配置一个IOC容器来解析DependerX的实例,使其注入RealThing,并用DecoratingThing修饰。 重要提示:每个DependerX 类型都需要将configuration的不同值传递给其RealThing的构造函数,例如“ConfigX” “在每种情况下。例如IoC容器完成的工作可能是:

new Depender1(new DecoratingThing(new RealThing("Config1")));
new Depender2(new DecoratingThing(new RealThing("Config2")));

......等等。

在Unity中,配置起来似乎很笨重,因为我必须在装饰器中混合装饰:

container.RegisterType<IThing, DecoratingThing>("ConfigX",
    new InjectionFactory(container => new DecoratingThing(new RealThing("ConfigX"));

container.RegisterType<DependerX>(
    new InjectionConstructor(new ResolvedParameter<IThing>("ConfigX");

对每个DependerX重复一次,很好地违反DRY。

我想做的是在RealThing的每个命名注册中删除在DecoratingThing的构造中嵌入IThing的构造的需要 - 并且只声明一次装饰。例如,如果装饰需要在将来更改,则更容易重新配置。我想出的最好的是这种注册帮助方法:

void RegisterDepender<TDepender>(IUnityContainer container, string config)
{
    container.RegisterType<TDepender>(new InjectionConstructor(
        new ResolvedParameter<IThing>(config)));
    container.RegisterType<IThing, DecoratingThing>(config,
        new InjectionFactory(c => new DecoratingThing(new RealThing(config))));
}

这至少消除了重复,但我仍然需要在RealThing内嵌入DecoratingThing的构造 - 这意味着我无法独立改变其生命周期。我无法再次注册IThing来执行此操作,因为我已经用完了该界面的注册名称。如果我想这样做,我必须引入另一组命名实例,如:

void RegisterDepender<TDepender>(IUnityContainer container, string config)
{
    string realConfig = "Real" + config;

    container.RegisterType<TDepender>(new InjectionConstructor(
        new ResolvedParameter<IThing>(config)));
    container.RegisterType<IThing, DecoratingThing>(config,
        new InjectionFactory(c => new DecoratingThing(
            container.Resolve<IThing>(realConfig))));
    container.RegisterType<IThing, RealThing>(realConfig,
        new ContainerControlledLifetimeManager(),
        new InjectionConstructor(config));
}

真的是最好的选择吗?感觉很复杂,并且对于那些将要追求的人来说可能很难。其他IoC容器是否有令人信服的方式来涵盖这种情况?由于每个DependerX重复注入工作的模式,有没有办法只在顶部(DependerX)级别使用命名实例?

还有其他意见吗?

2 个答案:

答案 0 :(得分:5)

班级设计本身似乎很合理。这是一个基于约定的容器配置,它基本上是这样做的:

public class MyConventions : UnityContainerExtension
{
    protected override void Initialize()
    {
        var dependers = from t in typeof(IThing).Assembly.GetExportedTypes()
                        where t.Name.StartsWith("Depender")
                        select t;

        foreach (var t in dependers)
        {
            var number = t.Name.TrimStart("Depender".ToArray());
            var realName = "Real" + number;
            var decoName = "Deco" + number;
            var config = "Config" + number;
            this.Container.RegisterType<IThing, RealThing>(realName, 
                new InjectionConstructor(config));
            this.Container.RegisterType<IThing, DecoratingThing>(decoName,
                new InjectionConstructor(
                    new ResolvedParameter<IThing>(realName)));
            this.Container.RegisterType(t,
                new InjectionConstructor(
                    new ResolvedParameter<IThing>(decoName)));
        }
    }
}

此配置将自动添加与上述谓词匹配的所有类,因此一旦设置完毕,您只需添加更多类(如Depender4Depender5),而无需重新访问容器配置一点都不。

以上配置满足以下单元测试:

[Fact]
public void ContainerCorrectlyResolvesDepender1()
{
    var container = new UnityContainer().AddNewExtension<MyConventions>();
    var actual = container.Resolve<Depender1>();

    var deco = Assert.IsAssignableFrom<DecoratingThing>(actual.Thing);
    var thing = Assert.IsAssignableFrom<RealThing>(deco.Thing);
    Assert.Equal("Config1", thing.Configuration);
}

[Fact]
public void ContainerCorrectlyResolvesDepender2()
{
    var container = new UnityContainer().AddNewExtension<MyConventions>();
    var actual = container.Resolve<Depender2>();

    var deco = Assert.IsAssignableFrom<DecoratingThing>(actual.Thing);
    var thing = Assert.IsAssignableFrom<RealThing>(deco.Thing);
    Assert.Equal("Config2", thing.Configuration);
}

[Fact]
public void ContainerCorrectlyResolvesDepender3()
{
    var container = new UnityContainer().AddNewExtension<MyConventions>();
    var actual = container.Resolve<Depender3>();

    var deco = Assert.IsAssignableFrom<DecoratingThing>(actual.Thing);
    var thing = Assert.IsAssignableFrom<RealThing>(deco.Thing);
    Assert.Equal("Config3", thing.Configuration);
}

答案 1 :(得分:3)

你有没有考虑过将装饰器基于Unity拦截功能?那么只用一次就可以很容易地说“使用这个拦截器拦截对IThing的调用”。

container.AddNewExtension<Interception>();
container.RegisterType<IThing>(new Interceptor<InterfaceInterceptor>(), new InterceptionBehavior<DecoratingThingBehavior>());

然后它会“将此IThing注入此Depender

container.RegisterType<Depender1>(new InjectionConstructor(new ResolvedParameter<IThing>("myNameForThing")));