如何为生产代码实现工厂,为单元测试实现依赖注入

How to implement Factory for production code, dependency injection for Unit Tests

我想让 class 的调用者能够按名称选择提供者,而不是像标准 DI 推荐的那样传递具体的提供者 class。它将允许对客户端隐藏实际的实现细节,仍然可以控制使用哪个提供者。我们通过实施工厂

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

constuctor 需要 providerName 作为参数。

但是对于单元测试,我希望使用替代品,而不是提供者的具体 class。 我最终得到了 2 个参数,负责相同的选择 - 生产代码的名称和测试调用的接口。

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

稍微替代的实现是从配置中读取 providerName 而不是将其作为参数传递。

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

我在想,是否存在更好的方法来使用单个参数来控制内部对象的创建,但避免将创建对象的责任交给客户端。

相关讨论 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

不确定我是否理解正确。

对我来说,听起来您想拥有 2 个不同的工厂。

首先创建一个接口:

public interface ICurrencyProviderFactory
{
    ICurrencyProvider Create()
}

然后创建配置工厂:

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

    public ICurrencyProvider Create()
    {
    }    
}

然后是单元测试工厂:

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

    public ICurrencyProvider Create()
    {
    }    
}

您的货币处理器应如下所示:

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

在您的 ServiceCollection 中或任何您解决依赖关系的地方,您应该包括正确的工厂。

因此,对于生产,您添加 ConfigurationCurrencyProviderFactory,对于单元测试,添加 UnitTestCurrencyProviderFactory。那么您的实际代码应该取决于 ICurrencyProviderFactory.

因为在你的构造函数中你是静态创建你的提供者,所以只需注入提供者。

按照您的描述创建一个工厂....

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

然后使用标准依赖注入:-

public class CurrencyProcessor
{
    private ICurrencyProvider _currencyProvider;

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

然后像这样使用

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

然后在你的测试中模拟它

var mock = new Mock<ICurrencyProvider>(). // mock stuff

你真正需要和你的工厂一起应用的是策略模式

interface ICurrencyProvider {
    //...members
}

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

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

工厂的实现将依赖于调用创建所需类型的策略集合。

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

这将允许更大的灵活性,因为可以注入任意数量的策略。

这里是一个CurrencyLayerWrapper策略的例子

public class CurrencyLayerWrapperProvider : ICurrencyProviderStrategy {
    public string Name { get { return "CurrencyLayerAPI"; } }
    public ICurrencyProvider Create() {
        return new CurrencyLayerWrapper();
    }
}