1

I have a code block inside a function. I want to refactor this to apply the factory pattern.

        IService service;     

        switch (path)
        {
            case ServicePath.service1:
                service = new service1(log, mappingConfig);
                return await service.ServiceTask();
            case ServicePath.service2:
                service = new service2(log, mappingConfig);
                return await service.ServiceTask();
            case ServicePath.service3:
                service = new service3(log, mappingConfig);
                return await service.ServiceTask();
            case ServicePath.service4:
                service = new service4(log, mappingConfig);
                return await service.ServiceTask();
        }

What I have done is,

        class ServiceFactory
        {
            public static IService CreateService(String path, ILogger log, IConfig mappingConfig)
            {
                case ServicePath.service1:
                    return new service1(log, mappingConfig);
                case ServicePath.service2:
                    return new service2(log, mappingConfig);                    
                case ServicePath.service3:
                    return new service3(log, mappingConfig);
                case ServicePath.service4:
                    return new service4(log, mappingConfig);
            }
        }

and then, the caller method will be

    IService service = ServiceFactory.CreateService(path, log, mappingConfig);
    return await serviceFeature.ServiceTask();

So my concern is, is it still be a factory pattern? If not, how to refactor this to be a factory pattern?

And I am reading everywhere, Factory Pattern and Dependency Injection does the same work. How to implement my code for dependency injection ?

Noor A Shuvo
  • 2,639
  • 3
  • 23
  • 48
  • You should remove `return await service.ServiceTask();` from inside your factory. I think they are forgotten there. And yes, then, this is factory pattern, but you can also consider a dependency injection container to do all the dirty work for you! – Oguz Ozgul May 03 '20 at 00:58
  • Side note: if you're using C#8, this code could be reduced quite a bit by using [switch expressions](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-8#switch-expressions) – devNull May 03 '20 at 01:02
  • @OguzOzgul, how can I achieve the same thing with Dependency Injection? – Noor A Shuvo May 03 '20 at 01:09
  • Use constructor injection for ILogger and IConfig. You can register types by name. AutoFac has support for this for example. I think all DIC frameworks will have the support. – Oguz Ozgul May 03 '20 at 01:13
  • related: https://stackoverflow.com/questions/60276277/is-it-forbidden-to-use-static-methods-in-factory-pattern – jaco0646 May 04 '20 at 16:52

3 Answers3

1

The approach that you currently have with the ServiceFactory is essentially an Abstract Factory since you are encapsulating the creation of a related set of objects behind a method.

There are a few different ways that you can achieve similar results with dependency injection. One of the main benefits that you would gain by doing this is that you no longer need to manually construct any of your Service classes. Here are some examples using the .NET Core DI framework:

Register a factory delegate

You can register Func<string, IService> which would take in a string and return an instance of IService:

services.AddTransient<Func<string, IService>>(sp => name =>
    name switch
    {
        "A" => sp.GetService<ServiceA>(),
        "B" => sp.GetService<ServiceB>(),
        _ => throw new NotImplementedException()
    });

You can then inject Func<string, IService> as a dependency wherever you need to use the factory.

Create a factory class

If you want the factory code in it's own class instead of within the DI registration, you can create a simple class that takes an instance of the IServiceProvider.

public interface IServiceFactory
{
    IService GetService(string name);
}

public class ServiceFactory : IServiceFactory
{
    private readonly IServiceProvider _provider;

    public ServiceFactory(IServiceProvider provider)
    {
        _provider = provider;
    }

    public IService GetService(string name) =>
        name switch
        {
            "A" => _provider.GetService<ServiceA>(),
            "B" => _provider.GetService<ServiceB>(),
            _ => throw new NotImplementedException()
        };
}

With this, you only need to register these types. And then you can inject IServiceFactory as needed.

devNull
  • 3,849
  • 1
  • 16
  • 16
1

The refactored code is of the type : Simple Factory or Static Factory. It would have been Factory Pattern if you have more than one Factory. That is, Say,ServiceFactory and ServiceFactory2 which also returns some IService

If you already know how to construct the parameters(path, log, mappingConfig) before itself, then you should stick to current SimpleFactory pattern itself. Dependence Injection makes more sense to me if the Parameters objects and its state are dynamic

James
  • 161
  • 1
  • 6
1

You can create an interface for service factory and implement it, instead of using a static method. Using an interface allows you to inject this dependency in any class and mock it for unit tests

public interface IServiceFactory
{
    IService CreateService(string path, ILogger log, IConfig mappingConfig);
}

Implementation might be the following

class ServiceFactory : IServiceFactory
{
    private readonly Dictionary<string, Func<ILogger, IConfig, IService>> _map;

    public ServiceFactory()
    {
        _map = new Dictionary<string, Func<ILogger, IConfig, IService>>();
        _map.Add(ServicePath.service1, (log, mappingConfig) => new service1(log, mappingConfig));
        _map.Add(ServicePath.service2, (log, mappingConfig) => new service2(log, mappingConfig));
        _map.Add(ServicePath.service3, (log, mappingConfig) => new service3(log, mappingConfig));
        _map.Add(ServicePath.service4, (log, mappingConfig) => new service4(log, mappingConfig));
    }

    public IService CreateService(string path, ILogger log, IConfig mappingConfig)
    {
        return _map.ContainsKey(path)
            ? _map[path](log, mappingConfig)
            : null; //or other default value
    }
}

Instead of switch statement and multiple case labels you can create a some kind of map Dictionary<string, Func<ILogger, IConfig, IService>>, which holds the service key and Func<ILogger, IConfig, IService> delegate to create a concrete Service. In CreateService method you are checking that service key is present in a dictionary and invoke a Func delegate to return a concrete service instance, otherwise return null

Pavel Anikhouski
  • 21,776
  • 12
  • 51
  • 66