【问题标题】:Whats the best practice for creating Stateless Utility classes in Java [closed]在 Java 中创建无状态实用程序类的最佳实践是什么 [关闭]
【发布时间】:2016-12-06 13:31:42
【问题描述】:

在 Java 中创建 Utility(不包含任何状态)类的最佳实践是什么。

在大多数情况下,我们最终会为此类任务创建静态方法。 其他可能的方式可能是“创建单例对象”来执行此操作。

当要求代码应该易于单元测试时,设计应该考虑什么?

【问题讨论】:

  • 每当您在 Java 或任何 OO 语言中看到实用程序类时,都可能表明遗漏了某些内容。也就是说,您的模型中有一些需要存在的对象。这是一个考虑可能是什么的机会,因此您可以使您的系统更具可测试性和灵活性。

标签: java design-patterns


【解决方案1】:

如果你能放纵一下我的比喻……

您可能以前见过其中之一:

请注意,我们称它为烤面包机。我们确实称它为“BreadUtil”。

同样,实用方法可以而且应该放在一个为特定功能命名的类中,而不是“与面包相关的杂项”。

大多数时候,你的静态方法属于一个相关的类;例如,Integer.parseInt 是 Integer 类的静态方法,而不是理论上的 IntegerUtil 或 NumberUtil 类的成员。

过去,创建单独的实用程序类的一种情况是当感兴趣的主要类是接口时。这方面的一个例子是java.util.Collections。但是,从 Java 8 开始,这不是借口,因为接口可以具有静态方法和默认方法。其实 Collections.sort(List) 已经迁移到List.sort了。

如果您有很多实用程序方法,并且您觉得它们会使相关类变得混乱,那么将它们放在单独的类中是可以的,但不要放在“BreadUtil”类中。将“util”一词放在类名(或“utils”、“utilities”、“misc”、“miscellaneous”、“general”、“shared”、“common”或“framework”)中是不可接受的.给类一个有意义的名称,描述这些方法的用途。如果方法过于多样化而不允许使用这样的类名,您可能需要将它们分成多个类。 (只有几个方法的小类是完全可以接受的;很多人甚至认为这是好的设计。)

回到 Integer 示例,如果您觉得这些方法使类变得杂乱无章,您可以像这样创建新类:

public class IntegerMath {
    private IntegerMath() { }

    public static int compare(int x, int y) { /* ... */ }
    public static int compareUnsigned(int x, int y) { /* ... */ }
    public static int divideUnsigned(int dividend, int divisor) { /* ... */ }
    public static int min(int a, int b) { /* ... */ }
    public static int max(int a, int b) { /* ... */ }
    public static int remainderUnsigned(int dividend, int divisor) { /* ... */ }
    public static int signum(int i) { /* ... */ }
    public static int sum(int a, int b) { /* ... */ }
    public static long toUnsignedLong(int i) { /* ... */ }
}

public class IntegerBits {
    private IntegerBits() { }

    public static int bitCount(int i) { /* ... */ }
    public static int highestOneBit(int i) { /* ... */ }
    public static int lowestOneBit(int i) { /* ... */ }
    public static int numberOfLeadingZeros(int i) { /* ... */ }
    public static int numberOfTrailingZeros(int i) { /* ... */ }
    public static int reverse(int i) { /* ... */ }
    public static int reverseBytes(int i) { /* ... */ }
    public static int rotateLeft(int i, int distance) { /* ... */ }
    public static int rotateRight(int i, int distance) { /* ... */ }
}

public class IntegerParser {
    private IntegerParser() { }

    public static int parseInt(String s) { /* ... */ }
    public static int parseInt(String s, int radix) { /* ... */ }
    public static int parseUnsignedInt(String s) { /* ... */ }
    public static int parseUnsignedInt(String s, int radix) { /* ... */ }
}

最后一个例子表明没有静态方法可能会更好:

public class IntegerParser {
    public IntegerParser() { this(10); }
    public IntegerParser(int radix) { /* ... */ }

    public int parseInt(String s) { /* ... */ }
    public int parseUnsignedInt(String s) { /* ... */ }
}

【讨论】:

  • 在大多数情况下,我真的很欣赏这个答案,但是这里有一个大胆的声明,即从不在类名中使用“Util”这个词。我想我的问题@VGR,您如何看待充满真正有用的实用程序类的 Apache 公共库? (mvnrepository.com/search?q=apache+commons)
  • @aaiezza 我对 Apache Commons 没有很高的评价。每个名称中带有“util”的类都可以有一个更好的名称,并且在许多情况下设计得更好。
【解决方案2】:

我认为最常见的方法是创建静态方法。例如,请参阅 StringUtils in Apache Commons LangStrings in Guava 甚至 Arrays in JDK

这个类也应该是final的,它应该有一个私有的构造函数来避免继承它或实例化它。

无论您使用静态方法还是单例,都应该对它进行单元测试。在后一种情况下,您可能会编写更多代码(字符)。

我知道 OO 纯粹主义者会争论此类类的存在,我倾向于同意他们的观点,但添加这些只是为了简单起见,您应该限制此类类的数量。

【讨论】:

  • Java 8 在接口中为我们带来了默认方法和静态方法,我们应该使用接口来服务实用方法吗?
  • 我会坚持类与 JDK 和其他知名库保持一致。你有什么理由改用接口吗?
【解决方案3】:

在 Java 中创建 Utility(不包含任何状态)类的最佳实践是什么。

在我看来,最好的方法是尽可能省略实用程序类。

实用程序类颠倒了面向对象编程的思想。当您需要一种新方法时,通常会将其添加到内聚度最高的类中。这意味着拥有该方法所需的大部分信息的类。其他信息将作为参数传递到方法中。如果参数列表过长,通常表明方法放错了位置。

在极少数情况下,您确实需要一个实用程序类来为某种类型提供方法。例如

  • 如果您无法将该方法添加到源代码中,因为您不拥有它(但您可以创建一个包装器或子类化它)
  • 如果需要附加方法的类是 final 并且您不拥有源代码(例如 StringUtility)

在大多数情况下,我们最终会为此类任务创建静态方法。其他可能的方式可能是“创建单例对象”来执行此操作。

当要求代码应该易于单元测试时,设计应该考虑什么?

如果您从单元可测试性的角度来看实用程序类,并且希望能够模拟实用程序类,您应该使用单例,因为可以替换对象引用。

通过更改单例实例引用(静态变量)或在客户端使用对象字段。例如

 private SomeUtility someUtility = SomeUtiltiy.INSTANCE;

 public void someMethod(...){
    // Can be replaced by changing the someUtility reference
    someUtility.doSomething(....); 

    // A static call like 
    // SomeUtility.doSomething(...);
    // can not be easily replaced.
 }

静态方法调用很难替换。一些测试框架,如powermock,通过重写客户端字节码来支持mocking of static calls。但我认为这些框架旨在支持不良遗留代码的单元测试。如果您需要 powermock 来编写新代码,您应该重新考虑您的设计。

【讨论】:

  • 如果您创建一个通用字符串或数组操作库怎么办?这通常是实用程序类的目的。对简单类型或对象的原始(或不那么原始)操作。
【解决方案4】:

如果你使用像 Spring 这样的框架,你可以创建一个带有@Service 注解的实用程序类。 这将确保它是单一实例 (SIngleton),并且是一种将它注入它的简单方法,它不会在其方法中需要任何其他类。

在任何其他情况下,我建议将其设为单例,使用工厂模式,或者在对比中,仅使用静态方法。

【讨论】:

    【解决方案5】:

    静态是一个单例。仅当您需要使用具有不同属性/设置值的多个实用程序类变体时,才需要具有非静态方法的单例实例。例如,当您的实用程序具有某些属性(即 customProperty)并且您同时需要两种不同的情况时。

    1. Utility 需要使用 customProperty=value1 时

    2. Utitlity 需要使用 customProperty=value2 时...

    但这很奇怪,笨拙且不好...实用程序调用者可以将所需的属性值提供给静态方法。 所以,不要坚持下去。使实用程序方法始终是静态的,并且不关心“理论”模式... :)

    【讨论】:

      猜你喜欢
      • 2022-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-05-09
      相关资源
      最近更新 更多