面向对象的程序设计替代工具类

工具类(或者说辅助类)是一种只有静态方法和封装,没有状态的结构。Apache Commons里的StringUtils、IOUtils、FileUtils,Guava里的Iterables和Iterators,以及JDK7里的Files都是工具类的完美例子。

这种设计思想在Java世界里非常普遍(C#、Ruby等亦是如此),因为工具类提供了任何地方都能使用的通用功能。

这里,我们想要遵从不要重复劳作的原则避免重复,所以,我们将公共代码块放到工具类里,必要时再次使用:

// This is a terrible design, don't reuse
public class NumberUtils {
  public static int max(int a, int b) {
    return a > b ? a : b;
  }
}

事实上,这真是一个方便的手段吗?!

工具类是邪恶的

然而,在面向对象的世界里,工具类被认为是不好的(甚至可以说是“糟糕的”)习惯。

已经有很多关于这个话题的讨论了,举几个名字:Nick Malik 的 Are Helper Classes Evil, Simon Hart 的 Why helper, singletons and utility classes are mostly bad, Marshal Ward 的 Avoiding Utility Classes, Dhaval Dalal 的 Kill That Util Class!, Rob Bagby 的 Helper Classes Are A Code Smell

此外, 在Stack Exchange有一些关于工具的问题:If a “Utilities” class is evil, where do I put my generic code?Utility Classes are Evil

所有他们的观点简单概括就是工具类不是严格意义上的对象,所以,他们不适合放到面向对象的世界里。它们继承了过程化编程,主要是因为那时我们习惯于功能分解的范式。

假如你同意这些观点并且想要停止使用工具类。我将通过例子展示这些工具这样被严格意义上的对象替换。

有关程序的例子

比如说,你想读取一个文本文件,按行分割,削减每行并且将结果保存到另一个文件里。这个可以用Apache Commons的FileUtils来做:

void transform(File in, File out) {
  Collection<String> src = FileUtils.readLines(in, "UTF-8");
  Collection<String> dest = new ArrayList<>(src.size());
  for (String line : src) {
    dest.add(line.trim());
  }
  FileUtils.writeLines(out, dest, "UTF-8");
}

上面的代码可能看起来很干净,然而,这是过程化编程而不是面向对象。我们操作数据(字节和位)时,每一行代码明确指示计算机从哪儿取数据然后放到哪里去。我们正在定义一个程序。

面向对象的选择

在一个面向对象的范式,我们应该实例化并组合对象,让它们管理的数据,而不是调用辅助静态函数,我们应该创建能够将我们需要的行为暴露出来的对象:

public class Max implements Number {
  private final int a;
  private final int b;
  public Max(int x, int y) {
    this.a = x;
    this.b = y;
  }
  @Override
  public int intValue() {
    return this.a > this.b ? this.a : this.b;
  }
}

这个过程调用:
int max = NumberUtils.max(10, 5);
将变成面向对象:
int max = new Max(10, 5).intValue();

对象代替数据结构

这是我用面向对象的方式设计和上面一样的文件转换功能:

void transform(File in, File out) {
  Collection<String> src = new Trimmed(
    new FileLines(new UnicodeFile(in))
  );
  Collection<String> dest = new FileLines(
    new UnicodeFile(out)
  );
  dest.addAll(src);
}

FileLines实现Collection并且封装所有文件读写操作。FileLines实例完全充当了一个字符串集合,并且隐藏了所有I/O操作。当我遍历它的时候文件开始读取,当我调用它的addAll()方法时文件开始写入。

Trimmed同样实现了Collection并且封装了一个字符串集合(装饰者模式)。每次下一行被检索时,它得到已修剪过的。

参与片段的所有类都相当小:Trimmed、FileLines和UnicodeFile。它们每一个为自己单一的作用负责,因此完美的符合单一职责原则。

站在我们的角度,作为库的使用者,这可能没那么重要,但是作为他们开发人员来说这是必要的,开发、维护和单元测试类FileLines比用一个在拥有80+个方法3000行代码的工具类FileUtils中的readLines()方法容易很多。说真的,看看它的源代码。

一个面向对象处理是可以延迟执行的。in文件没有被读取直到它的数据被需要的时候。如果我们因为I/O错误导致没能打开out文件,第一个文件是无法感知的。只有当我们调用allAll()时完整的展示才开始。

第二段的所有行,除了最后一行,实例化和组合小对象到大对象里。对象组合是相当廉价的对CPU而言,因为它不会引起任何数据的转换。

此外,显然第二段运行在O(1)的空间,而第一段需要执行O(n)里。这是我们有第一个处理数据的结果。

在面向对象是的世界里没有数据,只有对象和它们的行为。


本文是一篇译文,点击《OOP Alternative to Utility Classes》查看原文,如有翻译不当的地方欢迎指出。如需转载,请标明原文和译文的出处,谢谢。


微信扫一扫,查看更多内容
微信扫一扫 查看更多内容

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值