Question

I am currently stuck at trying to write a factory class that doesn't rely on service location.

The only other alternative I can think of is to use constructor injection to inject all possible instances, but that may lead to surprises as classes are passed via reference. It is also possibly going to be costly and messy once the number of possible providers grow.

The providers themselves are full complex classes that have their own dependencies so manual construction is out of the picture.

Updated service location example:

    public class ProviderFactory : IProviderFactory
    {
        private readonly IProviderConfigurationService _providerConfigurationService;

        public enum SearchType
        {
            Foo,
            Bar
        }

        public ProviderFactory(IProviderConfigurationService providerConfigurationService)
        {
            _providerConfigurationService = providerConfigurationService;
        }

        public Collection<IProvider> GetProviderInstances(SearchType searchType)
        {
            // Provider configuration service will read a XML/DB store to retrieve list of search providers applicable for a search type
            var providerList = _providerConfigurationService.GetProviderList(searchType);
            return new Collection<IProvider>(providerList.ForEach(x=> ServiceLocator.GetInstance(typeof(x))).ToList()) ;
        }
    }

What are my other options? I am currently using Unity for DI.

Was it helpful?

Solution

An alternative is to pass a Func<Type, object> to the constructor and to implement the function through your container:

unity.RegisterInstance<Func<Type, object>>(t => unity.Resolve(t))

Then in your class:

public ProviderFactory(Func<Type, object> createFunc, IProviderConfigurationService pcs)
{
    _createFunc = createFunc; 
}

public Collection<IProvider> GetProviderInstances(SearchType searchType)
{
    var providerList = _providerConfigurationService.GetProviderList(searchType);
    return new Collection<IProvider>(providerList.Select(_createFunc).ToList());
}

OTHER TIPS

You are missing an abstraction.

Your ProviderFactory should implement an IProviderFactory abstraction. This way you can place that interface in a base library of your application and you can place the ProviderFactory implementation inside your Composition Root. For code that lives inside your composition root, it is okay to reference the DI library, and in that case you're not using service location.

I have recently solved a very similar issue in my own code by using a DI framework. To satisfy Dependency Inversion, the factory constructor should accept an interface (as the other answers have said), but to get the framework to inject the right type is tricky without having a massive list of arguments detailing each possible concretion.

SimpleInjector allows you to register all concretions of a given abstraction with:

Container.RegisterCollection(typeof(IProvider), new [] {typeof(TKnown).Assembly,...});

Your XML could list the (possibly external) assemblies where the concretions are defined and you could build the assembly array from there. Then your factory just needs to accept them all and pick one, perhaps based on the searchType you mentioned.

public class ProviderFactory
{
    private List<IProvider> providers;
    public ProviderFactory(IEnumerable<IProvider> providers)
    {
        this.providers = providers.ToList();
    }

    public IProvider GetProvider(string searchType)
    {
        // using a switch here would open the factory to modification
        // which would break OCP
        var provider = providers.SingleOrDefault(concretion => concretion.GetType().Name == searchType);

        if (provider == null) throw new Exception("No provider found of that type.  Are you missing an assembly in the RegisterCollection for IProvider?");

        return provider;
    }

I know I'm way late to the party on this but assuming other folks don't see this approach as problematic, it might be useful.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top