依赖注入 - 从接口

时间:2016-11-28 18:34:21

标签: c# asp.net-mvc dependency-injection simple-injector

我正在对依赖注入+ 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 */
}

这仍然是一种反模式吗?

我应该做些什么改变?

这样做是错误的......

2 个答案:

答案 0 :(得分:1)

是;你正在包装服务定位器反模式,但它仍然是反模式。

相反,你应该让你的构造函数接受/etc/leinrc(大多数IoC容器支持这个)。

这样,你的ctor仍然列出了它的确切要求,而且在没有IoC的情况下直接创建你的类的实例仍然很容易。

答案 1 :(得分:1)

解决方案比您预期的要简单得多。

由于您注入的工厂包含一个Create<T>()方法,该方法可以请求的类型数量不受限制,因此这是Service Locator反模式的实现。

此外,CacheService组件真正需要的唯一服务是ICacheItemWrapperIFactory只增加了一个额外的间接层,增加了复杂性并使测试复杂化。

相反,您应该将ICacheItemWrapper直接注入CacheService的构造函数:

public CacheService(ICacheItemWrapper wrapper) { ... }

您可能认为这个答案太简单了,因为如果CacheService应该比ICacheItemWrapper更长,这会很容易导致问题。例如,如果CacheService注册为Singleton,而ICacheItemWrapperScoped,则将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>依赖项注入到应用程序组件中。