Java静态方法与Validator中的单例模式

huangapple go评论109阅读模式
英文:

Java Static Method vs. a Singleton in Validator

问题

为什么在所有情况下使用静态方法而不是单例被认为是如此反模式?

我最初写了以下代码

class MyValidator {
  public static boolean isValid(String mystring){
    if (一些条件...) {
      return true;
    } else {
      return false;
    }
  }
}

我实在看不出将这个方法变成一个对象的理由。它作为一个静态方法似乎已经很好了 - 更易于测试,消除状态等。

然而,在上游遇到一个问题,当我想编写控制器单元测试并模拟isValid()调用时。我意识到我可以使用像PowerMock这样的库,但人们似乎一贯地认为这样做是一个反模式。

为什么会这样,而且保持这种静态方法有什么问题吗?

英文:

Why is it considered such an anti-pattern in all cases to use static methods rather than singletons?

I originally wrote the following code

class MyValidator {
  public static boolean isValid(String mystring){
    if (some conditions ...) {
      return true
    } else {
      return false
    }
  }
}

I can't really see a reason to make this into an object. It seems just fine as a static method - more testable, eliminating state, etc.

However, I ran into an issue upstream when I wanted to write a controller unit test and mock out the isValid() call. I realize I can use libraries like PowerMock, but people seem to religiously think doing this is an antipattern.

Why is that and what's so wrong with keeping this static?

答案1

得分: 2

以下是翻译好的部分:

在所有情况下,使用静态方法而不是单例都是一种反模式。

这不是正确的。在某些情况下,静态方法是完全可以的,在其他情况下则不行。

以下是一些来自广泛使用的库的示例,这些情况下使用静态方法是完全可以的:

  • Apache Common Collections 中的 CollectionUtils
  • Apache Commons Lang 中的 StringUtils
  • Apache Commons IO 中的 FileUtils
  • Spring Framework 中的 ClassUtils
  • Hibernate 中的 PropertiesHelper

在什么情况下可以使用或不应该使用静态方法没有严格的标准。对于一些开发人员来说,这是一个个人偏好的问题,可以尽可能多地使用静态方法,或者反之,尽量避免使用静态方法。

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

1)提供的功能有多大可能需要在将来进行扩展或修改?如果您发现一些方法很有可能需要在某些上下文中被覆盖,那么使用非静态对象是有意义的。如果不太可能(例如,可能不太可能安全地比较两个字符串的方法,而不会引发 NPE),那么将这种功能放在静态方法中是可以的。
2)有多大可能会有更多具有类似功能的类?您是否可能需要某些通用功能,这些功能在不同的上下文中使用不同的实现(例如,可能需要多态性)?例如,将对象序列化为 JSON 可能是一个静态方法。但是,如果您预计很可能需要将对象序列化为 YML、XML、CSV、PDF 或其他某些格式,将这种功能放在非静态方法中是有意义的。
3)测试这种功能会有多容易?在某些情况下,测试可能很容易,例如测试一个比较两个字符串而不引发 NPE 的方法。在其他情况下,测试可能相当复杂,例如如果从正在测试的另一个代码中调用了这种方法。

关于 MyValidator 的情况:我会假设将来您可能需要多个验证器以及一些通用逻辑,该逻辑会迭代属性列表,确定每个属性的验证器并应用它们。使用静态方法很难实现这一点。而使用非静态方法可能会更容易。例如,定义此类结构可能会更容易:

public interface Validator<T> {
  boolean isValid(T target);
}

然后应用此类方法可能会很紧凑:

for (...) {
  validator = findValidatorForObject(obj);
  // 无需检查类型:String、Integer 等
  if (!validator.isValid(obj)) {
    ...
  }
}
英文:

> an anti-pattern in all cases to use static methods rather than
> singletons

This is not true. In some cases static methods are absolutely fine, in others not.

Here are some examples from widely used libraries when it is perfectly fine to use static methods:

  • CollectionUtils in Apache Common Collections
  • StringUtils in Apache Commons Lang
  • FileUtils in Apache Commons IO
  • ClassUtils in Spring Framework
  • PropertiesHelper in Hibernate

There are no strict criteria in what case static method can be used or should not be used. For some developers this is a matter of their personal preferences, to use static methods as much as possible, or vice versa, to avoid static methods as much as possible.

If you have no strong preferences, you may consider following criteria to decide.

  1. How likely it is that that the functionality provided by the needs to extended or modified in the future? If you see that it is very likely that some methods may need to overridden for some context, then it makes sense to use non-static objects. If it is unlikely (e.g. it is unlikely that the method that compares two strings safely, without throwing NPE), then it is fine to put such functionality in a static method.
  2. How likely it is that there will be more than one class with similar functionality? Is it likely that you may need some generic functionality that uses different implementations in different contexts (e.g. polymorphism may be needed)? For instance, serializing an object to JSON might be a static method. But if you expect that very likely serialization to YML, XML, CSV, PDF or some other formats will be needed, it makes sense to put make such functionality non-static.
  3. How easy would it be to test such functionality? In some cases testing can be easy, e.g. testing a methods that compares two strings without throwing NPE. In other cases it may be pretty complicated, e.g. if such method is called from another code that is being tested.

To your case with MyValidator: I would suppose that later on you may need more than one validator and some generic logic that iterates over list of attributes, determines validators for each attribute and applies them. With static methods it will be very hard to implement it. Where as with non-static methods it may be much easier. For instance, it would be easier to define a generic structure of such classes as following:

public interface Validator&lt;T&gt; {
  boolean isValid(T target);
}

Then applying such may be very compact:

for (...) {
  validator = findValidatorForObject(obj);
  // No need to check the type: String, Integer, ...
  if (!validator.isValid(obj)) {
    ...
  }
}

答案2

得分: 1

我认为你在静态方法与单例模式之间设立了一个错误的二分法。单例模式经常会产生问题,因为它们保存了对访问单例的类隐藏的状态。但作为静态方法的替代,它们不会保存状态。

另一种替代方案是定义一个接口,可以在生产代码中填充静态方法,在测试代码中填充模拟方法:

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

// 生产代码
ClassThatUsesValidator obj = new ClassThatUsesValidator(MyValidator::isValid);

// 测试代码
Validator mock = mock(Validator.class);
when(mock.isValid("foo")).thenReturn(false);
ClassThatUsesValidator testObj = new ClassThatUsesValidator(mock);
testObj.methodToTest("foo");
assertThat ...

通过这种方式,你避免了使用单例模式,使用了静态方法,并且仍然可以在测试中进行模拟。

英文:

I believe you're setting up a false dichotomy in static methods vs. singletons. Singletons often create problems because they hold state that is hidden from the clients of the classes that access the singletons. But as an alternative to static methods they would not hold state.

Another alternative is for you to define an interface that can be filled with your static method in production code and a mock in testing code:

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(&quot;foo&quot;)).thenReturn(false);
ClassThatUsesValidator testObj = new ClassThatUsesValidator(mock);
testObj.methodToTest(&quot;foo&quot;);
assertThat ...

This way you are avoiding a singleton, using a static method and are still able to mock it for testing.

huangapple
  • 本文由 发表于 2020年6月29日 06:52:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/62629041.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定