配置Unity以解析采用装饰依赖项的类型,该依赖项的参数随注入的类型而变化
这是一个相当简单的装饰器模式场景,其复杂性在于,装饰类型具有一个构造函数参数,该参数取决于它被注入的类型。
我有这样的界面:
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
类型,称为Depender1
, Depender2
等。
class DependerX() { public DependerX(IThing thing) { ... implementation ... } }
我想配置一个IOC容器来解析DependerX
实例,以便它们注入装饰有DecoratingThing
RealThing
。 重要提示:每个DependerX
类型都需要将不同的configuration
值传递给其RealThing
的构造函数,在每种情况下RealThing
说“ConfigX”。 例如,IoC容器完成的工作可能是:
new Depender1(new DecoratingThing(new RealThing("Config1"))); new Depender2(new DecoratingThing(new RealThing("Config2")));
… 等等。
在Unity中,这似乎很笨重的配置,因为我必须在装饰器中混合装饰:
container.RegisterType("ConfigX", new InjectionFactory(container => new DecoratingThing(new RealThing("ConfigX")); container.RegisterType( new InjectionConstructor(new ResolvedParameter("ConfigX");
对于每个DependerX
,重复DependerX
,很好地违反DRY。
我想要做的是在每个命名的IThing
注册中IThing
需要在构造DecoratingThing
中嵌入RealThing
的构造 – 并且只需要修改一次装饰。 例如,如果装饰需要在将来更改,则更容易重新配置。 我想出的最好的是这个帮助注册方法:
void RegisterDepender(IUnityContainer container, string config) { container.RegisterType(new InjectionConstructor( new ResolvedParameter(config))); container.RegisterType(config, new InjectionFactory(c => new DecoratingThing(new RealThing(config)))); }
这至少消除了重复,但我仍然需要在DecoratingThing
嵌入RealThing
的构造 – 这意味着我不能独立地改变他们的生命周期。 我无法再次注册IThing
,因为我已经用完了该界面的注册名称。 如果我想这样做,我必须引入另一组命名实例,如下所示:
void RegisterDepender(IUnityContainer container, string config) { string realConfig = "Real" + config; container.RegisterType(new InjectionConstructor( new ResolvedParameter(config))); container.RegisterType(config, new InjectionFactory(c => new DecoratingThing( container.Resolve(realConfig)))); container.RegisterType(realConfig, new ContainerControlledLifetimeManager(), new InjectionConstructor(config)); }
这真的是最好的选择吗? 感觉很复杂,并且对于那些将要追求的人来说可能很难。 其他IoC容器是否有令人信服的方式来涵盖这种情况? 由于每个DependerX重复注入工作的模式,有没有办法只在顶层( DependerX
)级别使用命名实例?
还有其他意见吗?
课堂设计本身似乎很合理。 这是一个基于约定的容器配置,它基本上是这样做的:
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(realName, new InjectionConstructor(config)); this.Container.RegisterType(decoName, new InjectionConstructor( new ResolvedParameter(realName))); this.Container.RegisterType(t, new InjectionConstructor( new ResolvedParameter (decoName))); } } }
此配置将自动添加与上述谓词匹配的所有类,因此一旦设置完毕,您可以添加更多类(如Depender4
或Depender5
),而无需重新访问容器配置。
以上配置满足以下unit testing:
[Fact] public void ContainerCorrectlyResolvesDepender1() { var container = new UnityContainer().AddNewExtension(); var actual = container.Resolve(); var deco = Assert.IsAssignableFrom(actual.Thing); var thing = Assert.IsAssignableFrom(deco.Thing); Assert.Equal("Config1", thing.Configuration); } [Fact] public void ContainerCorrectlyResolvesDepender2() { var container = new UnityContainer().AddNewExtension(); var actual = container.Resolve(); var deco = Assert.IsAssignableFrom(actual.Thing); var thing = Assert.IsAssignableFrom(deco.Thing); Assert.Equal("Config2", thing.Configuration); } [Fact] public void ContainerCorrectlyResolvesDepender3() { var container = new UnityContainer().AddNewExtension(); var actual = container.Resolve(); var deco = Assert.IsAssignableFrom(actual.Thing); var thing = Assert.IsAssignableFrom(deco.Thing); Assert.Equal("Config3", thing.Configuration); }
你有没有想过将装饰器基于Unity拦截function? 那么只用一次就可以很容易地说“使用这个拦截器拦截对IThing
的调用”。
container.AddNewExtension(); container.RegisterType(new Interceptor(), new InterceptionBehavior());
然后它会“将这个IThing
注入这个和那个Depender
”
container.RegisterType(new InjectionConstructor(new ResolvedParameter("myNameForThing")));