我正在对依赖注入+ mvc进行一些测试,我有一个问题。
在服务中,我如何创建一个对象(槽接口),这将是该方法的结果?
我发现抽象工厂可以解决这个问题,但有些人说它是反模式的,而且它是服务定位器。
我做过这样的事情:
public class ObjectFactory : IFactory
{
readonly Container container;
public ObjectFactory(Container container)
{
this.container = container;
}
public T Create<T>()
where T : class
{
return (T)this.container.GetInstance<T>();
}
}
然后我像这样使用它:
public class CacheService : ICacheService
{
readonly IFactory factory;
public CacheService(IFactory factory)
{
this.factory = factory;
}
private void Insert(string name, object value)
{
if (this.CacheAvailable())
{
Remove(name);
// This line where I ask for the container to resolve this interface
ICacheItemWrapper item = factory.Create<ICacheItemWrapper>();
item.Value = value;
HttpRuntime.Cache.Insert(name, item, null, System.Web.Caching.Cache.NoAbsoluteExpiration, System.Web.Caching.Cache.NoSlidingExpiration);
}
}
/* HIDDEN CODE */
}
这仍然是一种反模式吗?
我应该做些什么改变?
这样做是错误的......
答案 0 :(得分:1)
是;你正在包装服务定位器反模式,但它仍然是反模式。
相反,你应该让你的构造函数接受/etc/leinrc
(大多数IoC容器支持这个)。
这样,你的ctor仍然列出了它的确切要求,而且在没有IoC的情况下直接创建你的类的实例仍然很容易。
答案 1 :(得分:1)
解决方案比您预期的要简单得多。
由于您注入的工厂包含一个Create<T>()
方法,该方法可以请求的类型数量不受限制,因此这是Service Locator反模式的实现。
此外,CacheService
组件真正需要的唯一服务是ICacheItemWrapper
。 IFactory
只增加了一个额外的间接层,增加了复杂性并使测试复杂化。
相反,您应该将ICacheItemWrapper
直接注入CacheService
的构造函数:
public CacheService(ICacheItemWrapper wrapper) { ... }
您可能认为这个答案太简单了,因为如果CacheService
应该比ICacheItemWrapper
更长,这会很容易导致问题。例如,如果CacheService
注册为Singleton
,而ICacheItemWrapper
为Scoped
,则将ICacheItemWrapper
注入CacheService
会导致到Captive Dependency。
如果发生这种情况,您可能会将ICacheItemWrapper
依赖项更改为Func<ICacheItemWrapper>
,但这样做会导致整个应用程序发生彻底变更。此外,Func<T>
依赖是一个漏洞抽象,因为现在该依赖的消费者现在知道该依赖的“挥发性”。这应该与其消费者无关。注入Func<T>
也会使消费者及其测试变得复杂,现在必须处理委托返回抽象而不是简单地处理抽象。
在Captive Dependency的情况下,解决方案是将短期组件包装到一个代理类中,该类在依赖上创建组件。这样,消费者不会受到这种变化的影响。例如:
public class CacheItemWrapperProxy : ICacheItemWrapper
{
private readonly Func<ICacheItemWrapper> wrapperProvider;
public CacheItemWrapperProxy(Func<ICacheItemWrapper> wrapperProvider) {
this.wrapperProvider = wrapperProvider;
}
// ICacheItemWrapper method(s)
public object GetItem(string key) => this.wrapperProvider().GetItem(key);
}
CacheItemWrapperProxy
实现ICacheItemWrapper
,这允许它被注入其消费者,而消费者不必改变。请注意CacheItemWrapperProxy
本身确实取决于Func<T>
,但使用隔离的Func<T>
和CacheItemWrapperProxy
可以位于应用的Composition Root内。同样,应用程序的其余部分将不受影响。
这是您在Simple Injector中注册的方法:
container.Register<ICacheItemWrapper, CacheItemWrapperImpl>(Lifestyle.Scoped);
container.RegisterDecorator<ICacheItemWrapper, CacheItemWrapperProxy>(Lifestyle.Singleton);
container.Register<ICacheService, CacheService>(Lifestyle.Singleton);
请注意,Simple Injector没有为注入Func<T>
依赖项提供开箱即用的支持。这是故意的,因为 - 如上所述 - 您的应用程序组件不应该依赖于Func<T>
。该规则的例外是Simple Injector的装饰器注册工具。 RegisterDecorator
方法确实具有处理Func<T>
依赖项的本机支持,唯一的情况是此T
是装饰类型(在您的情况下为ICacheItemWrapper
)。
长话短说,总是使用构造函数注入,并防止将Func<T>
依赖项注入到应用程序组件中。