Java 具有易于模拟的默认实现的可配置依赖项

Java 具有易于模拟的默认实现的可配置依赖项,java,unit-testing,dependency-injection,mocking,Java,Unit Testing,Dependency Injection,Mocking,我正在研究一种参数值解析器库。我希望将解析器定义如下: public class Parser { private ValuesConfiguration configuration; private ValuesProvider valuesProvider; private ValuesMapper valuesMapper; public Parser(ValuesConfiguration configuration) { this.c

我正在研究一种参数值解析器库。我希望将解析器定义如下:

public class Parser {

    private ValuesConfiguration configuration;
    private ValuesProvider valuesProvider;
    private ValuesMapper valuesMapper;

    public Parser(ValuesConfiguration configuration) {
        this.configuration = configuration;
    }

    public Result parse(String parameterName) {
        List<Values> values = valuesProvider.getValues(parameterName);
        // do other stuff on values
        // ...
        return valuesMapper.transformValues(values, configuration);
    }
}
public Parser(ValuesConfiguration configuration) {
    this(configuation, new DefaultValuesProvider());
}

public Parser(ValuesConfiguration configuration, ValuesProvider valuesProvider) {
    this.configuration = configuration;
    this.valuesProvider = valuesProvider;
}


public Result parse(String parameterName) {
    List<Values> values = valuesProvider.getValues(parameterName);
    // do other stuff on values
    // ...
    return valuesMapper.transformValues(values, configuration);
}
尽管必须有可能在需要时设置自己的实现。我想知道我应该如何以及在哪里初始化这些默认实现,并且如果客户需要,仍然让他们自己设置。我不想坚持下去

new DefaultValuesProvider()
因为默认实现将访问文件系统,所以很难进行测试(模拟)。我知道我可以使用setter(比如在DI中),但是默认值呢

编辑:
在回答完所有问题后,我想最好在这里设置setter,让客户机提供自己的ValuesProvider和ValuesMapper实现。但是如何创建默认实现呢?我想将实例化与逻辑解耦,所以我不想在这里使用新的DefaultValueProvider()。工厂模式在这里适用吗?如果是这样的话,我应该在哪里使用它?

使用Google Guice或类似的依赖注入框架:

它非常适合你的需要。您可以配置几个模块,描述将为特定接口实例化哪些实现。例如,您可以拥有ProductionModule和UnitTestModule。

如何:

public void setValuesProvider(ValuesProvider valuesProvider) {
    this.valuesProvider = valuesProvider;
}

public Result parse(String parameterName) {
    if (valuesProvider == null) {
        valuesProvider = new DefaultValuesProvider();
    }

    List<Values> values = valuesProvider.getValues(parameterName);
    // do other stuff on values
    // ...
    return valuesMapper.transformValues(values, configuration);
}

客户端需要依赖Guice。如果客户机使用Spring呢?DI框架不能很好地共存。如果Guice被用作服务定位器,那么模块就有一个商定的全局位置。最好有一个全局工厂,可以由客户端重新分配。您也可以使用spring。Spring具有与Guice相似的特性。我相信Spring的ApplicationContext提供了Guice的模型功能(参见此处的示例:)。基斯比我干净多了。依赖注入比工厂强大得多。更少的样板文件,巨大的灵活性,非常好的可读代码。一旦你尝试了,就没有回头路了。OP正在制作一个库,要求客户端使用特定的DI框架是错误的。感谢你在这里指出DI框架,但我不想知道,对于这样小的库来说,使用Spring或Guice是否不太重?Guice非常轻量。如果您有一个或两个类,那么这是真的-需要额外的依赖项和配置可能会带来比好处更多的开销。事实上,我错过了“图书馆”这个词(我应该更仔细地阅读它),我同意在这种情况下,这将是一个过度杀伤力。。。我的不好,很抱歉给你带来困惑…+1但很难把它写在简历上。“知道如何做基本的事情”。这可能是当今罕见的质量。更好的选择可能是重载构造函数。这将使您能够使依赖项成为final/readonly。属性注入为不变量提供了非常弱的保证,例如,您可以不断更改对同一实例的依赖关系,而这可能不是您想要的。这部分是我所想的,但我希望能够将逻辑与对象构造解耦。DI框架对于如此小的库来说似乎太多了。重载构造函数似乎可以,但仍然需要提供默认值。可能是默认抽象工厂,并将其作为将创建与逻辑解耦的依赖项?@meriton,将valuesProvider和valuesMapper作为依赖项,将导致构造函数数量激增,如您所述。使用setter注入方案无法更改默认实现。相关:stackoverflow.com/questions/2045904/dependency injectdi-friendly library/2047657#2047657
public Parser(ValuesConfiguration configuration) {
    this(configuation, new DefaultValuesProvider());
}

public Parser(ValuesConfiguration configuration, ValuesProvider valuesProvider) {
    this.configuration = configuration;
    this.valuesProvider = valuesProvider;
}


public Result parse(String parameterName) {
    List<Values> values = valuesProvider.getValues(parameterName);
    // do other stuff on values
    // ...
    return valuesMapper.transformValues(values, configuration);
}
public void setValuesProvider(ValuesProvider valuesProvider) {
    if (this.valuesProvider != null) {
        throw new IllegalStateException("Dependency already set");
    }
    this.valuesProvider = valuesProvider;
}