每当我觉得自己对oop有信心时,突然间我就被一些先进的例子咬了一口。就像鲍勃叔叔写的这篇非常棒的文章,他用下面的一个例子来说明他的卡塔。
public class WordWrapper {
private int length;
public WordWrapper(int length) {
this.length = length;
}
public static String wrap(String s, int length) {
return new WordWrapper(length).wrap(s);
}
public String wrap(String s) {
if (length < 1)
throw new InvalidArgument();
if (s == null)
return "";
if (s.length() <= length)
return s;
else {
int space = s.indexOf(" ");
if (space >= 0)
return breakBetween(s, space, space + 1);
else
return breakBetween(s, length, length);
}
}
private String breakBetween(String s, int start, int end) {
return s.substring(0, start) +
"\n" +
wrap(s.substring(end), length);
}
public static class InvalidArgument extends RuntimeException {
}
}
我有以下疑问:
为什么使用静态助手方法?
为什么
wrap
类是嵌套的和静态的?为什么我们甚至需要初始化这个类,因为它只不过是一个算法,并且可以在没有任何实例变量的情况下操作,为什么我们需要它的~100个实例(例如)?
最佳答案
为什么静态助手方法要包装?
没有特别好的理由——我认为这是一种主观判断:
WordWrapper.wrap("foo", 5);
比
new WordWrapper(5).wrap("foo");
(我同意这一点)。当代码感觉非常重复时,我倾向于添加这样的方法。
然而,静态表单可能会导致隐藏的问题:在循环中调用它会导致创建许多不必要的
WordWrapper
实例,而非静态表单只创建一个并重用它。为什么
InvalidArgument
类是嵌套的和静态的?它被嵌套的含义是,它只用于报告
WordWrapper
中方法的无效参数例如,如果某个与数据库相关的类抛出WordWrapper.InvalidArgument
的实例,这就没有多大意义。请记住,为了方便起见,如果适当导入,可以将其引用为
InvalidArgument
;您仍然始终使用some.packagename.WordWrapper.InvalidArgument
,因此在其他类中使用它没有语义意义。如果希望在其他类中使用它,则不应嵌套它。
至于为什么:我可以想到两个原因(它们是同一枚硬币的不同侧面):
它不需要是非静态的非静态嵌套类称为内部类。它与创建它的包含类的实例相关;在某种程度上,内部类中的数据与外部类中的数据相关。
这实际上意味着,在创建外部类时,会有一个对其传递到内部类的隐藏引用。如果不需要引用此实例,请将其设为静态,这样就不会传递引用这就像删除方法中未使用的参数:如果不需要,就不要传递它。
持有此引用会产生意外的后果。(我将此作为一个单独的点,因为前一个点指的是一个逻辑要求/设计,是否供参考,这指的是持有该参考的实际含义)。
就像保留任何引用一样,如果有对内部类实例的引用,则会使其引用的所有内容都不符合垃圾收集的条件,因为它仍然是可访问的。根据您如何使用内部类的实例,这可能会导致内存泄漏类的静态版本不存在此问题,因为没有引用:当清除所有的
static
实例时,您可以引用InvalidArgument
。另一个结果是
Wrapper
的契约无效:InvalidArgument
是Throwable
的超类,实现了InvalidArgument
,这意味着Serializable
也实现了InvalidArgument
。但是,Serializable
不是WordWrapper
。因此,由于对Serializable
的非空引用,非静态InvalidArgument
的序列化将失败。解决这两个问题的简单方法是使嵌套类成为静态类;作为防御策略,应该使所有嵌套类都成为静态类,除非您真的不需要它们。
为什么我们甚至需要初始化这个类,因为它只不过是一个算法…
好问题这与您的第一个问题有点关系:您可以只使用静态helper方法,并删除实例方法和状态。
在放弃实例方法之前,实例方法比静态方法有很多优点。
显而易见的一点是,您可以在实例中存储状态,例如
WordWrapper
。这允许您向static
传递更少的参数,这可能会减少代码的重复性;我想这会产生一种类似于部分求值的效果。(您也可以将状态存储在静态变量中,但是全局可变状态是一个皇家pita;这是另一个故事)。静态方法是紧密耦合的:使用
length
的类紧密绑定到单词包装的特定实现。出于许多目的,一个实现可能是好的。但是,几乎总是有至少两个实现(生产和测试实现)的情况。
因此,尽管以下内容与一个实现紧密绑定:
void doStuffWithAString(String s) {
// Do something....
WordWrapper.wrap(s, 100);
// Do something else ....
}
以下可以在运行时提供实现:
void doStuffWithAString(WordWrapper wrapper, String s) {
// Do something....
wrapper.wrap(s);
// Do something else ....
}
它使用
wrap
作为strategy。现在,您可以选择用于特定情况的单词包装算法(例如,一种算法适用于英语,而另一种算法适用于汉语-也许,我不知道,它只是一个示例)。
或者,对于测试,您可以为只返回参数的测试注入一个模拟实例-这允许您测试
WordWrapper
,而不同时测试wrapper
的实现。但是,灵活性来自于开销。静态方法更简洁。对于非常简单的方法,静态方法很可能是一种方法;随着方法变得越来越复杂(尤其是在测试用例中,计算输入以获得对测试用例很重要的特定输出变得越来越困难),实例方法表单成为更好的选择。
归根结底,没有硬性规定可供使用注意两者,并注意在特定情况下哪个最有效。