如何为生产代码实现工厂,为单元测试实现依赖注入
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();
}
}
我想让 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();
}
}