0

I want to give a caller of my class ability to choose provider by name, instead of passing the provider concrete class as standard DI recommends. It will allow to hide actual implementation details from client, still giving control which provider to use. We've done it by implementing factory

public ICurrencyProvider GetCurrencyServiceProvider(string providerName)
    {
        switch (providerName)
        {
            case "CurrencyLayerAPI":
                {  currencyService = new CurrencyLayerWrapper(); }
                break;
            case "XE":
                { currencyProvider = new XEWrapper(); }
                break;
            }
        return _currencyProvider;
    }

and constuctor expects providerName as a parameter.

However for unit tests I wish to use Substitute, not concrete class of provider. I ended up with 2 parameters, responsible for the same choice- name for production code and interface for calls from tests.

    public CurrencyProcessor(string providerName, ICurrencyProvider substituteCurrencyProvider =null)
   {
          if(!providerName .IsNullOrEmpty()) 
          {         
             _currencyProvider = GetCurrencyServiceProvider(providerName);
            }
          else
          {  _currencyProvider =substituteCurrencyProvider; 
          }
    }

Slightly alternative implementation is to read providerName from configuration instead of passing it as a parameter.

public CurrencyProcessor(IConfigurationProvider configurationProvider,  ICurrencyProvider substituteCurrencyProvider =null)
 {
     _providerName = _configurationProvider.GetAppSetting("CurrencyProviderToUse"); 
      if(!providerName .IsNullOrEmpty()) 
      {         
         _currencyProvider = GetCurrencyServiceProvider(providerName);
      }
      else
      {  _currencyProvider =substituteCurrencyProvider;
      }
}

I wander, is any better way exist to have single parameter to control creation of internal object, but avoiding giving responsibility to create object to a client.

Related discussions How to use Dependency Injection without breaking encapsulation?
Preferable way of making code testable: Dependency injection vs encapsulation
https://softwareengineering.stackexchange.com/questions/344442/dependency-injection-with-default-construction

Michael Freidgeim
  • 26,542
  • 16
  • 152
  • 170

3 Answers3

1

since in your constructor your are statically creating your provider, just inject the provider.

create a factory as you describe....

 public class CurrencyFactory
    {
        public static ICurrencyProvider  GetCurrencyServiceProvider(string providerName)
        {
            return null;
        }
    }

then use standard dependency injection :-

public class CurrencyProcessor
{
    private ICurrencyProvider _currencyProvider;

    public CurrencyProcessor(ICurrencyProvider currencyProvider)
    {
        _currencyProvider = currencyProvider;
    }
}

and then use like so

var p = new CurrencyProcessor(CurrencyFactory.GetCurrencyServiceProvider("bitcoin"));

then in your test mock it

var mock = new Mock<ICurrencyProvider>(). // mock stuff
Keith Nicholas
  • 43,549
  • 15
  • 93
  • 156
0

Not sure if I understand it correct.

For me it sounds like you want to have 2 different Factories.

First create a Interface:

public interface ICurrencyProviderFactory
{
    ICurrencyProvider Create()
}

Then create a Configuration Factory:

public class ConfigurationCurrencyProviderFactory : ICurrencyProviderFactory
{
    public ConfigurationCurrencyProviderFactory(IConfigurationProvider configuration)
    {    
    }

    public ICurrencyProvider Create()
    {
    }    
}

And then a UnitTest Factory:

public class UnitTestCurrencyProviderFactory : ICurrencyProviderFactory
{
    public UnitTestCurrencyProviderFactory()
    {    
    }

    public ICurrencyProvider Create()
    {
    }    
}

Your currency processor should look like this:

public CurrencyProcessor(ICurrencyProviderFactory factory)
{
    _currencyProvider = factory.Create();
}

In your ServiceCollection or whereever you resolve your dependencies you should include the correct factory.

So for Production, you add the ConfigurationCurrencyProviderFactory, for UnitTest the UnitTestCurrencyProviderFactory. Your actual code then should depend on ICurrencyProviderFactory.

Christian Gollhardt
  • 16,510
  • 17
  • 74
  • 111
0

What you actually need to apply along with your factory is the Strategy Pattern

interface ICurrencyProvider {
    //...members
}

public interface ICurrencyProviderStrategy {
    string Name { get; }
    ICurrencyProvider Create();
}

public interface ICurrencyProviderFactory {
    ICurrencyProvider GetCurrencyServiceProvider(string providerName);
}

An implementation of the factory would depend on a collection of strategies to call upon to create the desired types.

public class CurrencyProviderFactory : ICurrencyProviderFactory {
    private readonly IEnumerable<ICurrencyProviderStrategy> strategies;

    public CurrencyProviderFactory(IEnumerable<ICurrencyProviderStrategy> strategies) {
        this.strategies = strategies;
    }

    public ICurrencyProvider GetCurrencyServiceProvider(string providerName) {
        var provider = strategies.FirstOrDefault(p => p.Name == providerName);
        if (provider != null)
            return provider.Create();
        return null;
    }
}

This would allow greater flexibility as any number of strategies can be injected.

Here is an example of a CurrencyLayerWrapper Strategy

public class CurrencyLayerWrapperProvider : ICurrencyProviderStrategy {
    public string Name { get { return "CurrencyLayerAPI"; } }
    public ICurrencyProvider Create() {
        return new CurrencyLayerWrapper();
    }
}
Nkosi
  • 235,767
  • 35
  • 427
  • 472