Java静态方法与验证器中的单例
为什么在所有情况下使用静态方法而不是单例方法都被认为是一种反模式 我最初编写了以下代码Java静态方法与验证器中的单例,java,Java,为什么在所有情况下使用静态方法而不是单例方法都被认为是一种反模式 我最初编写了以下代码 class MyValidator { public static boolean isValid(String mystring){ if (some conditions ...) { return true } else { return false } } } 我真的看不出有什么理由把它变成一个物体。作为一种静态方法,它似乎很好——更易于测试,
class MyValidator {
public static boolean isValid(String mystring){
if (some conditions ...) {
return true
} else {
return false
}
}
}
我真的看不出有什么理由把它变成一个物体。作为一种静态方法,它似乎很好——更易于测试,消除状态,等等
然而,当我想编写控制器单元测试并模拟isValid()调用时,我遇到了一个问题。我意识到我可以使用像PowerMock这样的库,但人们似乎虔诚地认为这样做是一种反模式
为什么会这样?保持这个静止有什么错
反模式在所有情况下都使用静态方法,而不是
单身汉
事实并非如此。在某些情况下,静态方法是绝对好的,而在另一些情况下则不是
下面是一些广泛使用的库中的示例,使用静态方法是非常好的:
- Apache公共集合中的CollectionUtils
- Apache Commons Lang中的StringUtils
- Apache Commons IO中的FileUtils
- Spring框架中的ClassUtils
- 属性帮助器处于休眠状态
如果你没有强烈的偏好,你可以考虑下面的标准来决定。
public interface Validator<T> {
boolean isValid(T target);
}
我相信你在静态方法和单例方法之间建立了一种错误的二分法。单例常常会产生问题,因为它们持有对访问单例的类的客户端隐藏的状态。但作为静态方法的替代方法,它们不会保持状态 另一种选择是定义一个接口,该接口可以在生产代码中使用静态方法填充,在测试代码中使用模拟方法填充:
interface Validator {
boolean isValid(String string);
}
class ClassThatUsesValidator {
private final Validator validator;
public ClassThatUsesValidator(Validator validator) {
this.validator = validator;
}
public void methodToTest(String value) {
if (validator.isValid(value))
...
}
}
// production code
ClassThatUsesValidator obj = new ClassThatUsesValidator(MyValidator::isValid);
// test code
Validator mock = mock(Validator.class);
when(mock.isValid("foo")).thenReturn(false);
ClassThatUsesValidator testObj = new ClassThatUsesValidator(mock);
testObj.methodToTest("foo");
assertThat ...
这样,您就避免了使用静态方法的单例,并且仍然能够模拟它进行测试。它可以实现更好的测试。如果您创建了一个可注入的单例对象(也称为服务),那么您可以模拟它来测试代码的其他复杂部分。然后,我将所有静态方法替换为可以正常模拟的实例,并将powermock作为依赖项删除。使代码更清晰,测试更可靠,运行速度也更快。但是,如果powermock适合您,那么您可以随心所欲。除了@cs95的评论之外,使用对象使模拟成为可能,这是保持复杂组件测试正常和简单所必需的。对象是可配置的。也许有一天您希望允许将最大长度传递给验证。对于静态方法,这意味着您必须添加一个参数,这会破坏现有代码,或者添加一个带有附加参数的新方法,如果最终有很多东西需要配置,这可能会失控。如果使用非静态方法,只需添加
setMaxLength(int)
等,而无需更改现有的公共方法。
interface Validator {
boolean isValid(String string);
}
class ClassThatUsesValidator {
private final Validator validator;
public ClassThatUsesValidator(Validator validator) {
this.validator = validator;
}
public void methodToTest(String value) {
if (validator.isValid(value))
...
}
}
// production code
ClassThatUsesValidator obj = new ClassThatUsesValidator(MyValidator::isValid);
// test code
Validator mock = mock(Validator.class);
when(mock.isValid("foo")).thenReturn(false);
ClassThatUsesValidator testObj = new ClassThatUsesValidator(mock);
testObj.methodToTest("foo");
assertThat ...