可配置的依赖项,易于模拟的默认实现

时间:2011-07-01 22:53:55

标签: java unit-testing dependency-injection mocking

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

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

我希望这个库客户端不知道ValuesProvider和ValuesMapper默认实现并使用它像

Result result = new Parser(myConfig).parse("sampleParam");

尽管必要时可以设置自己的实现。我想知道如何以及在何处启动这些默认实现,并且仍然允许客户端根据需要设置自己的实现。我不想坚持

new DefaultValuesProvider()

等。在构造函数中,因为默认实现将例如访问文件系统,因此很难测试(模拟它们)。我知道我可以使用setter(比如在DI中)但是默认情况呢?

编辑: 在你的所有答案之后,我想这里最好让setter允许客户提供他们自己的ValuesProvider和ValuesMapper实现。但是如何创建默认实现?我想将实例化与逻辑解耦,所以我不想在这里使用新的DefaultValueProvider()。工厂模式适用于此处吗?如果是这样,我应该如何以及在哪里使用它?

2 个答案:

答案 0 :(得分:2)

怎么样:

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

Mark指出,构造函数注入可能是更好的方法。这看起来如下:

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

我也同意他对优势的总结:

  

这将使您能够使依赖关系最终/只读。 Property Injection提供了关于不变量的非常弱的保证 - 例如你可以继续改变对同一个实例的依赖性,这可能不是你想要的。

然而,也有缺点:

  1. 构造函数的数量是可选参数数量的指数,导致参数的大量委托和重复的JavaDoc。因此,如果可选依赖项很少,我只会使用构造函数注入。你有2,这就是我对这种事情的痛苦阈值。
  2. 构造函数注入不是非常友好的子类,因为子类构造函数必须重新声明超类依赖项。在您的情况下,希望公开所有配置选项的子类将需要4个构造函数......如果它添加了一个可选参数,那么最好不要使用构造函数注入,因为这需要另外4个构造函数。
  3. 因此,我的建议是使用setter注入,如果有必要阻止重新分配依赖项,请执行以下操作:

    public void setValuesProvider(ValuesProvider valuesProvider) {
        if (this.valuesProvider != null) {
            throw new IllegalStateException("Dependency already set");
        }
        this.valuesProvider = valuesProvider;
    }
    

答案 1 :(得分:0)

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

http://code.google.com/p/google-guice/

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