以OOP方式思考

时间:2016-04-27 17:16:03

标签: java algorithm oop

每当我认为我对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 {
  }
}

我有以下怀疑:

  1. 为什么使用静态辅助方法wrap
  2. 为什么InvalidArgument类是嵌套的和静态的?
  3. 为什么我们甚至需要初始化这个类,因为它只是一个算法而且可以在没有任何实例变量的情况下运行,为什么我们需要~100个实例(例如)呢?

1 个答案:

答案 0 :(得分:2)

  
      
  1. 为什么静态助手方法会换行?
  2.   

没有特别好的理由 - 我认为这是一个主观判断:

WordWrapper.wrap("foo", 5);

更整洁
new WordWrapper(5).wrap("foo");

(我同意的是)。当代码感觉非常重复时,我倾向于发现自己添加这样的方法。

但是,静态表单可能会导致隐藏的问题:在循环中调用它会导致创建许多不必要的WordWrapper实例,而非静态表单只创建一个并重用它。 / p>

  
      
  1. 为什么InvalidArgument类是嵌套的和静态的?
  2.   

它嵌套的含义是它仅用于报告WordWrapper中方法的无效参数。例如,如果某个与数据库相关的类抛出WordWrapper.InvalidArgument的实例,那就没有多大意义。

请记住,如果适当导入,您可以将其引用为InvalidArgument以方便使用;你仍然总是使用some.packagename.WordWrapper.InvalidArgument,所以它在其他课程中的使用并没有语义上的意义。

如果您希望在其他类中使用它,则不应嵌套。

至于为什么static:我可以想到两个原因(这是同一枚硬币的不同方面):

  1. 它不需要是非静态的。非静态嵌套类称为内部类。它与创建它的包含类的实例有关;在某种程度上,内部类中的数据与外部类中的数据相关。

    这实际上意味着在创建内部类时传递给内部类的隐藏引用。如果您永远不需要引用此实例,请将其设置为静态,因此不会传递引用。这就像删除未使用的方法参数一样:如果您不需要它,请不要通过它。

  2. 持有此引用会产生意外后果。 (我将此作为一个单独的点来绘制,因为前一个指的是参考的逻辑要求/设计与否,这指的是持有该参考的实际含义)。

    就像持有任何引用一样,如果你有一个内部类的实例的引用,你就会使 it 引用的所有内容都不符合垃圾回收的条件,因为它仍然可以访问。根据您使用内部类的实例的方式,这可能会导致内存泄漏。该类的静态版本不会遇到此问题,因为没有引用:当InvalidArgument的所有实例都被清除时,您可以引用Wrapper。 / p>

    另一个结果是InvalidArgument的合同无效:Throwable,一个超级类InvalidArgument,实现Serializable,意味着InvalidArgument也实现了Serializable {1}}。但是,WordWrapper不是Serializable。因此,由于对InvalidArgument的非空引用,非静态WordWrapper的序列化将失败。

    解决这两个问题的简单方法是创建嵌套类static;作为一种防御策略,人们应该将所有嵌套类静态化,除非你真的需要它们不存在。

  3.   
        
    1. 为什么我们甚至需要初始化这个类,因为它只是一个算法......
    2.   

    好问题。这与你的第一个问题有关:你可以只使用静态辅助方法,并删除实例方法和状态。

    在丢弃实例方法之前,实例方法优于静态方法。

    显而易见的是,您可以在实例中存储状态,例如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

    但是,灵活性带来了开销。静态方法更简洁。对于非常简单的方法,静态很可能是要走的路;随着方法变得越来越复杂(特别是在测试用例中,为了获得对您的测试用例而言非常重要的特定输出,输出输入变得越来越难),实例方法表单变得更好。

    最终,没有严格的规则可供使用。请注意这两者,并注意哪些在特定情况下效果最佳。