在Java中创建不持有任何状态的工具类的最佳实践是什么。
在大多数情况下,我们会为这些任务创建静态方法。另一个可能的方法是“创建单例对象”来执行此操作。
当要求代码易于单元测试时,应考虑哪些设计因素?
在Java中创建不持有任何状态的工具类的最佳实践是什么。
在大多数情况下,我们会为这些任务创建静态方法。另一个可能的方法是“创建单例对象”来执行此操作。
当要求代码易于单元测试时,应考虑哪些设计因素?
如果你能容忍我使用一个比喻……
你可能以前见过这样的东西:
注意我们称其为烤面包机,而不是“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) { /* ... */ }
}
我认为最常见的方法是创建静态方法。例如,可以参考Apache Commons Lang中的StringUtils、Guava中的Strings或者甚至是JDK中的Arrays。
此外,类应该被声明为final,并且应该有一个私有构造函数以避免继承或实例化它。
无论是使用静态方法还是单例模式,对于单元测试来说应该是相同的工作量。在后一种情况下,你可能需要编写更多的代码(字符)。
我知道OO纯粹主义者会质疑这些类的存在,我倾向于同意他们的看法,但这些类仅仅是为了简化而添加的,你应该限制这类类的数量。
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
)通过重写客户端字节码来支持模拟静态调用。但我认为这些框架是为了支持对糟糕的遗留代码进行单元测试而设计的。如果您需要为新代码使用powermock,您应该重新考虑您的设计。
静态是单例的。只有当您需要多个具有不同属性/设置值的实用程序类变体时,才需要具有非静态方法的单例实例。例如,当您的实用程序具有某些属性(即customProperty),并且您需要同时使用两种不同情况时。
当实用程序需要使用customProperty = value1时
当实用程序需要使用customProperty = value2时...
但这很奇怪,笨拙而不好...实用程序调用者可以将所需的属性值提供给静态方法。因此,请不要固守那个理论模式...使实用程序方法始终为静态,并且不必关心“理论”模式... :)