每当我觉得自己对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的契约无效:InvalidArgumentThrowable的超类,实现了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的实现。
但是,灵活性来自于开销。静态方法更简洁。对于非常简单的方法,静态方法很可能是一种方法;随着方法变得越来越复杂(尤其是在测试用例中,计算输入以获得对测试用例很重要的特定输出变得越来越困难),实例方法表单成为更好的选择。
归根结底,没有硬性规定可供使用注意两者,并注意在特定情况下哪个最有效。

10-06 03:41