Leandro Soares
Leandro Soares

Reputation: 2972

Dependency Injection - Create object from interface

I'm doing some tests to dependency injection + mvc and I have a question.

Inside a service, how may I create an object (trough interface) which will be the result of the method?

I've discovered that an abstract factory may resolve this but some people say that it's an anti-pattern and it's a service-locator.

I've done something like this:

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>();
    }
}

then I use it like this:

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 */
}

Is this still an anti-pattern?

What changes should I do?

It feels wrong to do it like this...

Upvotes: 3

Views: 3751

Answers (2)

Steven
Steven

Reputation: 172646

The solution is much simpler than you would expect.

Since the factory you are injecting contains a Create<T>() method that is unconstraint in the number of types that can be requested, this is an implementation of the Service Locator anti-pattern.

Furthermore, the only service that your CacheService component really needs is the ICacheItemWrapper. The IFactory only adds an extra layer of indirection that increases complexity and complicates testing.

Instead you should inject ICacheItemWrapper directly into the constructor of CacheService:

public CacheService(ICacheItemWrapper wrapper) { ... }

You might think this answer is too simplistic, since this would obvously lead to problems in case your CacheService should outlive the ICacheItemWrapper. For instance, in case the CacheService is registered as Singleton, while the ICacheItemWrapper is Scoped, injecting the ICacheItemWrapper into the CacheService will lead to a Captive Dependency.

When this happens, you might be tempted to change the ICacheItemWrapper dependency to a Func<ICacheItemWrapper> but doing so can cause sweeping changes throughout your application. Furthermore, a Func<T> dependency is a leaky abstraction, since now the consumer of that dependency now has knowledge about the 'volatiliness' of that dependency. This should be of no concern to its consumers. Injecting a Func<T> also complicates the consumers and their tests that now have to deal with a delegate returning the abstraction instead of simply having to deal with the abstraction.

In the case of a Captive Dependency, the solution is to wrap the short-lived component into a proxy class that creates the component on depend. This way consumers are unaffected to this change. Example:

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);
}

The CacheItemWrapperProxy implements ICacheItemWrapper and this allows it to be injected into its consumers, without the consumers having to change. Do note that the CacheItemWrapperProxy itself does depend on Func<T>, but the use of this Func<T> isolated and the CacheItemWrapperProxy can be located inside the application's Composition Root. Again, the rest of the application will be unaffected.

This is how you register this in Simple Injector:

container.Register<ICacheItemWrapper, CacheItemWrapperImpl>(Lifestyle.Scoped);
container.RegisterDecorator<ICacheItemWrapper, CacheItemWrapperProxy>(Lifestyle.Singleton);
container.Register<ICacheService, CacheService>(Lifestyle.Singleton);

Do note that Simple Injector has no out-of-the-box support for injecting Func<T> dependencies. This is deliberate, since -as explained above- your application components should not depend on Func<T>. The exception to the rule is Simple Injector's decorator registration facility. The RegisterDecorator method does have native support for handling Func<T> dependencies, in the sole case that this T is the decorated type (in your case ICacheItemWrapper).

Long story short, always use constructor injection, and prevent injecting Func<T> dependencies into your application components.

Upvotes: 1

SLaks
SLaks

Reputation: 887433

Yes; you're wrapping the Service Locator anti-pattern, but it's still an antipattern.

Instead, you should make your constructor accept a Func<ICacheItemWrapper> (most IoC containers support this).

This way, your ctor still lists its exact requirements, and it's still easy to create an instance of your class directly without IoC.

Upvotes: 1

Related Questions