每当我认为我对OOP有了一些信心时,我突然被一些先进的例子所困扰。就像鲍勃叔叔非常伟大的article一样,他使用下面的课程作为他的kata的一个例子。
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
?InvalidArgument
类是嵌套的和静态的?答案 0 :(得分:2)
- 为什么静态助手方法会换行?
醇>
没有特别好的理由 - 我认为这是一个主观判断:
WordWrapper.wrap("foo", 5);
比
更整洁new WordWrapper(5).wrap("foo");
(我同意的是)。当代码感觉非常重复时,我倾向于发现自己添加这样的方法。
但是,静态表单可能会导致隐藏的问题:在循环中调用它会导致创建许多不必要的WordWrapper
实例,而非静态表单只创建一个并重用它。 / p>
- 为什么
醇>InvalidArgument
类是嵌套的和静态的?
它嵌套的含义是它仅用于报告WordWrapper
中方法的无效参数。例如,如果某个与数据库相关的类抛出WordWrapper.InvalidArgument
的实例,那就没有多大意义。
请记住,如果适当导入,您可以将其引用为InvalidArgument
以方便使用;你仍然总是使用some.packagename.WordWrapper.InvalidArgument
,所以它在其他课程中的使用并没有语义上的意义。
如果您希望在其他类中使用它,则不应嵌套。
至于为什么static
:我可以想到两个原因(这是同一枚硬币的不同方面):
它不需要是非静态的。非静态嵌套类称为内部类。它与创建它的包含类的实例有关;在某种程度上,内部类中的数据与外部类中的数据相关。
这实际上意味着在创建内部类时传递给内部类的隐藏引用。如果您永远不需要引用此实例,请将其设置为静态,因此不会传递引用。这就像删除未使用的方法参数一样:如果您不需要它,请不要通过它。
持有此引用会产生意外后果。 (我将此作为一个单独的点来绘制,因为前一个指的是参考的逻辑要求/设计与否,这指的是持有该参考的实际含义)。
就像持有任何引用一样,如果你有一个内部类的实例的引用,你就会使 it 引用的所有内容都不符合垃圾回收的条件,因为它仍然可以访问。根据您使用内部类的实例的方式,这可能会导致内存泄漏。该类的静态版本不会遇到此问题,因为没有引用:当InvalidArgument
的所有实例都被清除时,您可以引用Wrapper
。 / p>
另一个结果是InvalidArgument
的合同无效:Throwable
,一个超级类InvalidArgument
,实现Serializable
,意味着InvalidArgument
也实现了Serializable
{1}}。但是,WordWrapper
不是Serializable
。因此,由于对InvalidArgument
的非空引用,非静态WordWrapper
的序列化将失败。
解决这两个问题的简单方法是创建嵌套类static
;作为一种防御策略,人们应该将所有嵌套类静态化,除非你真的需要它们不存在。
- 为什么我们甚至需要初始化这个类,因为它只是一个算法......
醇>
好问题。这与你的第一个问题有关:你可以只使用静态辅助方法,并删除实例方法和状态。
在丢弃实例方法之前,实例方法优于静态方法。
显而易见的是,您可以在实例中存储状态,例如length
。这允许您将更少的参数传递给wrap
,这可能会使代码重复性降低;我想它的效果有点像部分评估。 (你也可以在静态变量中存储状态,但全局可变状态是皇家PITA;这是另一个故事。)
静态方法是紧耦合:使用WordWrapper
的类紧密绑定到自动换行的特定实现。
出于许多目的,一个实现可能没问题。但是,至少有两种实现(生产和测试实现)几乎总是存在。
因此,以下内容与一个实现紧密相关:
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 ....
}
使用wrapper
作为strategy。
现在,您可以选择用于特定情况的自动换行算法(例如,一种算法适用于英语,但另一种算法适用于中文 - 也许,我不知道,它只是一种例子)。
或者,对于测试,您可以为仅返回参数的测试注入模拟实例 - 这允许您在不测试doStuffWithAString
的实现的情况下测试WordWrapper
。
但是,灵活性带来了开销。静态方法更简洁。对于非常简单的方法,静态很可能是要走的路;随着方法变得越来越复杂(特别是在测试用例中,为了获得对您的测试用例而言非常重要的特定输出,输出输入变得越来越难),实例方法表单变得更好。
最终,没有严格的规则可供使用。请注意这两者,并注意哪些在特定情况下效果最佳。