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
  • 属性帮助器处于休眠状态
在什么情况下可以使用或不应该使用静态方法,并没有严格的标准。对于一些开发人员来说,这是他们个人喜好的问题,尽量使用静态方法,反之亦然,尽量避免使用静态方法

如果你没有强烈的偏好,你可以考虑下面的标准来决定。

  • 未来需要扩展或修改由用户提供的功能的可能性有多大?如果您看到某些方法很可能需要为某些上下文重写,那么使用非静态对象是有意义的。如果不太可能(例如,在不抛出NPE的情况下,比较两个字符串的方法不太可能安全),那么可以将此类功能放在静态方法中
  • 有多大可能会有多个具有类似功能的类?您是否可能需要一些在不同环境中使用不同实现的通用功能(例如,可能需要多态性)?例如,将对象序列化为JSON可能是一种静态方法。但是,如果您认为很可能需要对YML、XML、CSV、PDF或其他格式进行序列化,那么将此类功能设置为非静态是有意义的
  • 测试这种功能有多容易?在某些情况下,测试可能很容易,例如,测试一个比较两个字符串而不抛出NPE的方法。在其他情况下,它可能相当复杂,例如,如果从正在测试的另一个代码调用此方法
  • 对于MyValidator的情况:我想稍后您可能需要不止一个验证器和一些通用逻辑来迭代属性列表,确定每个属性的验证器并应用它们。使用静态方法将很难实现它。与非静态方法一样,这可能更容易。例如,更容易定义此类类的通用结构,如下所示:

    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 ...