OOP Alternative to Utility Classes

The following text is a partial translation of the original English article, performed by ChatGPT (gpt-3.5-turbo) and this Jekyll plugin:

工具类(也称为辅助类)是一个仅包含静态方法且不封装状态的“结构”。来自Apache CommonsStringUtilsIOUtilsFileUtils;来自GuavaIterablesIterators;以及JDK7中的Files都是工具类的完美例子。

这种设计思想在Java世界(以及C#,Ruby等)非常流行,因为工具类提供了在各个地方都使用的常见功能。

在这里,我们希望遵循DRY原则并避免重复。因此,我们将常见的代码块放入工具类中,在需要时重复使用它们。

确实,这是一种非常方便的技术!

然而,在面向对象的世界中,实用类被认为是一种非常糟糕(甚至可以说是“可怕”的)做法。

对于这个主题已经有很多讨论,以下是其中一些:Are Helper Classes Evil? by Nick Malik,Why helper, singletons and utility classes are mostly bad by Simon Hart,Avoiding Utility Classes by Marshal Ward,Kill That Util Class! by Dhaval Dalal,Helper Classes Are A Code Smell by Rob Bagby。

此外,StackExchange 上也有一些关于实用类的问题:If a “Utilities” class is evil, where do I put my generic code?Utility Classes are Evil

所有这些论点的简要总结是实用类不是合适的“对象”,因此它们并不适合面向对象的世界。它们继承自“过程式”编程,主要是因为我们当时习惯了功能分解的范式。

假设您同意这些论点,并想停止使用实用类,我将通过示例展示如何用合适的对象替代这些实用类。

举个例子,假设你想要读取一个文本文件,将其按行分割,修剪每一行,然后将结果保存到另一个文件中。这可以通过 Apache Commons 的 FileUtils 来完成。

上面的代码看起来很整洁,但实际上这是过程式编程,而不是面向对象的编程。我们正在操作数据(字节和位),并在每一行代码中明确地指示计算机从哪里获取数据,然后将其放在哪里。我们正在定义执行的过程

在面向对象的范式中,我们应该实例化和组合对象,从而让它们自行管理数据的时间和方式。我们不应该调用辅助的静态函数,而是应该创建能够展示我们所寻求行为的对象。

这个程序性调用:

将变为面向对象的:

土豆,土豆?并不真的像土豆;请继续阅读…

以下是我用面向对象的方式设计相同的文件转换功能:

FileLines implements Collection<String> and encapsulates all file reading and writing operations. An instance of FileLines behaves exactly as a collection of strings and hides all I/O operations. When we iterate it—a file is being read. When we addAll() to it—a file is being written.

Trimmed also implements Collection<String> and encapsulates a collection of strings (Decorator pattern). Every time the next line is retrieved, it gets trimmed.

所有参与代码片段的类都相当小:TrimmedFileLinesUnicodeFile。它们每个都负责自己的一个功能,因此完全符合单一职责原则

对于我们作为库的用户来说,这可能不是那么重要,但对于开发人员来说,这是必不可少的。与在一个包含80多个方法和3000行代码的工具类FileUtils中使用readLines()方法相比,开发、维护和单元测试FileLines类要容易得多。真的,看一下它的源代码

面向对象的方法支持惰性执行。只有在需要数据时,才会读取in文件。如果由于某种I/O错误而无法打开out,第一个文件甚至不会被触及。只有在调用addAll()之后,整个过程才开始。

第二个代码片段中的所有行(最后一行除外)都会实例化和组合较小的对象形成更大的对象。这种对象组合对于CPU来说相当廉价,因为它不会引起任何数据转换。

此外,很明显,第二个脚本在O(1)空间内运行,而第一个脚本在O(n)内执行。这是我们对第一个脚本中的数据采用过程化方法的结果。

在面向对象的世界中,不存在数据;只有对象和它们的行为!

Translated by ChatGPT gpt-3.5-turbo/42 on 2023-11-18 at 05:12

sixnines availability badge   GitHub stars