我目前正在尝试在装饰器中包装一个类,并在运行时注入其中一个依赖项。我目前有IStorage
的界面,由StorageCacheDecorator
和Storage
实施。 StorageCacheDecorator
接收IStorage
和Storage object takes in a
Context`对象。 但是,每次解析这些类时都需要传递上下文对象。
public interface IStorage
{
}
public class Storage : IStorage
{
public Context Context { get; }
public Storage(Context context)
{
this.Context = context;
}
}
public class StorageCacheDecorator : IStorage
{
public IStorage InnerStorage { get; }
public StorageCacheDecorator(IStorage innerStorage)
{
this.InnerStorage = innerStorage;
}
}
public class Context
{
}
我省略了实现细节,下面的测试给出了我的问题的一个例子
[Test]
public void ShouldResolveWithCorrectContext()
{
var context = new Context();
var container = new UnityContainer();
container.RegisterType<Storage>();
container.RegisterType<IStorage>(
new InjectionFactory(c => new StorageCacheDecorator(
c.Resolve<Storage>())));
var resolve = container.Resolve<IStorage>(new DependencyOverride<Context>(context));
Assert.That(resolve, Is.TypeOf<StorageCacheDecorator>());
var cacheDecorator = ((StorageCacheDecorator)resolve);
Assert.That(cacheDecorator.InnerStorage, Is.TypeOf<Storage>());
var storage = ((Storage)cacheDecorator.InnerStorage);
Assert.That(storage.Context, Is.SameAs(context));
}
但是,如果我们删除装饰器,则测试通过
[Test]
public void ShouldResolveWithCorrectContext1()
{
var context = new Context();
var container = new UnityContainer();
container.RegisterType<IStorage, Storage>();
var resolve = container.Resolve<IStorage>(new DependencyOverride<Context>(context));
Assert.That(resolve, Is.TypeOf<Storage>());
Assert.That(((Storage)resolve).Context, Is.SameAs(context));
}
如何让InjectionFactory
尊重DependencyOverride
?
答案 0 :(得分:3)
首先,我很确定你(以及今天 - 我)偶然发现的是Unity中的一个错误。
我设法通过this excellent article从我得到一个BuilderStrategy
的例子中得到了一点诊断。在我用这个扩展替换了我的InjectionFactory之后,它适用于某些情况,并且不适用于其他情况。
经过一些调查后,似乎Unity中的所有分辨率都针对相同的Container
及其配置,并且任何DependencyOverrides
都会在IBuilderContext
对象的调用中拖动resolverOverrides
集,其中包含policies
构建的DependencyOverrides
。 IBuilderContext
提供了GetResolverOverride(Type)
方法,该方法允许实现获取给定Type的值覆盖。
因此,如果您明确要求IBuilderContext.GetResolverOverride
覆盖您的存储Context
,您将获得您期望的相同上下文对象。
但是,如果您尝试询问Container本身,您将获得一个根据标准规则解析的Context对象。不是那种在分辨率上被覆盖的。
这就是为什么在InjectionFactory委托中container.Resolve(..)
的任何尝试都像这样:
container.RegisterType<IStorage>(
new InjectionFactory(c => new StorageCacheDecorator(
c.Resolve<Storage>()))); // <-- this C is Container
将无法满足覆盖,因为.. Container不知道覆盖!
必须是:
container.RegisterType<IStorage>(
new InjectionFactory(c => new StorageCacheDecorator(
builderContext.Resolve<Storage>())));
如果实现了,可以访问GetResolverOverride
并使用正确的覆盖构建正确的存储。但是,不存在这样的方法,更糟糕的是,在此代码中您无权访问IBuilderContext
- InjectionFactory并不能提供给您。可能只有扩展程序和朋友可以访问它。
有趣的事实:IBuilderContext
有GetResolverOverride
但没有任何种类的.Resolve
。因此,如果您从该文章中获取代码,并且如果您使用该文章中的PreBuildUp
来执行您自己的解析逻辑,那么您必须使用Container(并且在解析器覆盖时失败),或者深入研究一切,并手动完成构建实例所需的所有子分辨率。 IBuilderContext
为您提供了一个漂亮的NewBuildUp()
方法,这似乎是一个很好的捷径,但它使用基础容器而不转发解析器覆盖。
看到它是多么复杂和不直观,以及不小心丢弃那组解析器和回退到vanilla上下文是多么容易,我很确定InjectionFactory是EVIL / BUGGED / MISDESIGNED / etc:它给你“c “,您的主要Container实例,因此您无法根据覆盖正确解析参数。我们应该获得某种派生容器或额外的构建器对象等,而不是那个主容器。
使用文章中的代码,我能够破解使用此GetResolverOverride
以我想要的方式初始化新对象实例的东西,但它绝不是通用的,完全不可重复使用(我只是调用了正确的.GetResolverOverride来获取值并将其直接传递到new MyObject(value)
..但是,上帝,这太可怕了。但是,我需要作为测试设置的一部分,所以我可以忍受它,直到代码得到重构。
现在,让我们回到你的案子。显然你可以做类似的事情,但事实证明,在装饰器的情况下,有一个更简单的方法:只需摆脱InjectionFactory。您的原始实例初始化代码可能更复杂,但如果它实际上只是在您的示例中那么简单:
container.RegisterType<IStorage>(
new InjectionFactory(c =>
new StorageCacheDecorator( // <- NEW &
c.Resolve<Storage>() // <- RESOLVE
)));
你应该实际使用声明式方式而不是命令式错误注射工具:
container.RegisterType<IStorage, StorageCacheDecorator>(
new InjectionConstructor(
new ResolvedParameter<Storage>()
));
净效果完全相同:创建新对象,调用单参数构造函数,并解析Storage
并将其用作该参数。我尝试了你的测试用例,它运行得很好。
您也可以使用直接对象实例,而不是'ResolvedParameter`,例如:
container.RegisterType<IStorage, StorageCacheDecorator>(
new InjectionConstructor(
"foo", 5, new Shoe()
));
但是,就像原始示例中的new StorageDecorator
一样,InjectionConstructor需要获取构造函数的所有参数,显然我们不会得到编译时错误当构造函数参数在将来发生变化时。它比InjectionFactory更有限,因为它需要预先指定所有参数。
仇恨团结的更多理由..