我看到了这个问题:如果“实用程序”类是邪恶的,我应该把我的通用代码放在哪里?
我想,为什么实用程序类是邪恶的?
假设我有一个包含几十个类的域模型。我需要能够 xml-ify 实例。我是否在父级上创建一个 toXml 方法?我要创建一个 MyDomainXmlUtility.toXml 辅助类吗?这是业务需求跨越整个领域模型的情况——它真的属于实例方法吗?如果应用程序的 XML 功能上有一堆辅助方法呢?
我看到了这个问题:如果“实用程序”类是邪恶的,我应该把我的通用代码放在哪里?
我想,为什么实用程序类是邪恶的?
假设我有一个包含几十个类的域模型。我需要能够 xml-ify 实例。我是否在父级上创建一个 toXml 方法?我要创建一个 MyDomainXmlUtility.toXml 辅助类吗?这是业务需求跨越整个领域模型的情况——它真的属于实例方法吗?如果应用程序的 XML 功能上有一堆辅助方法呢?
实用程序类并不完全是邪恶的,但它们可能违反构成良好面向对象设计的原则。在一个好的面向对象设计中,大多数类应该代表一个事物及其所有属性和操作。如果您正在操作一个事物,那么该方法可能应该是该事物的成员。
但是,有时您可以使用实用程序类将许多方法组合在一起——例如,java.util.Collections
该类提供了可在任何 Java 集合上使用的许多实用程序。这些并不特定于一种特定类型的 Collection,而是实现可用于任何 Collection 的算法。
确实,您需要做的是考虑您的设计并确定将方法放在哪里最有意义。通常,它是作为类内部的操作。但是,有时,它确实是一个实用程序类。但是,当您确实使用实用程序类时,不要只是将随机方法放入其中,而是按用途和功能组织方法。
我认为普遍的共识是实用程序类本身并不邪恶。你只需要明智地使用它们:
将静态实用程序方法设计为通用且可重用。确保它们是无国籍的;即没有静态变量。
如果您有很多实用方法,请将它们划分为类,以便开发人员轻松找到它们。
不要使用领域类中的静态或实例方法是更好的解决方案的实用程序类。例如,考虑抽象基类或可实例化辅助类中的方法是否是更好的解决方案。
从 Java 8 开始,接口中的“默认方法”可能是比实用程序类更好的选择。(例如,请参阅何时使用:Java 8+ 接口默认方法与抽象方法。)
看待这个问题的另一种方式是观察在引用的问题中,“如果实用程序类是“邪恶的””是一个稻草人论点。就像我在问:
“如果猪会飞,我应该带伞吗?”。
在上面的问题中,我实际上并不是说猪可以飞……或者我同意他们可以飞的命题1。
典型的“xyz 是邪恶的”陈述是修辞手段,旨在通过提出极端观点让你思考。它们很少(如果有的话)用作文字事实的陈述。
1 - 你不应该将稻草人问题解释为在户外时是否应该随身携带雨伞的建议。
实用程序类是有问题的,因为它们未能将职责与支持它们的数据分组。
但是它们非常有用,我一直将它们构建为永久结构或在更彻底的重构过程中作为垫脚石。
从清洁代码的角度来看,实用程序类违反了单一职责和开闭原则。它们有很多改变的理由,并且在设计上是不可扩展的。它们真的应该只在重构过程中作为中间产物存在。
我想当它开始变得邪恶时
1)它变得太大(在这种情况下,只需将它们分组到有意义的类别中)。
2)存在不应该是静态方法的方法
但只要不满足这些条件,我认为它们是非常有用的。
你可以从两个角度来看这个问题:
*Util
方法通常是不良代码设计或惰性命名约定的暗示。util
类/模块。外部库示例假设您正在编写管理贷款和信用卡的应用程序。来自这些模块的数据通过 Web 服务以json
格式公开。理论上,您可以手动将对象转换为 in 中的字符串json
,但这会重新发明轮子。正确的解决方案是在两个模块中包含用于将 java 对象转换为所需格式的外部库。(在示例图像中我展示了 gson)
util
类/模块。写你自己的util
,没有任何借口给其他团队成员作为一个用例假设我们需要在应用程序的两个模块中执行一些计算,但它们都需要知道波兰什么时候有公共假期。理论上,您可以在模块内进行这些计算,但最好将此功能提取到单独的模块中。
这是一个很小但很重要的细节。您编写的类/模块不是称为HolidayUtil
,而是PolishHolidayCalculator
. 从功能上讲,它是一个util
类,但我们设法避免了通用词。
实用程序类很糟糕,因为它们意味着您懒得为该类想一个更好的名称:)
话虽如此,我很懒惰。有时你只需要完成工作而你的大脑一片空白……那就是“实用”课程开始出现的时候。
我不完全同意实用程序类是邪恶的。
虽然实用程序类可能在某些方面违反了 OO 原则,但它们并不总是坏事。
例如,假设您想要一个函数来清除与 value 匹配的所有子字符串的字符串x
。
STL C++(截至目前)不直接支持这一点。
您可以创建std::string
.
但问题是,您真的希望您在项目中使用的每个字符串都成为您的扩展字符串类吗?
有时 OO 并没有真正的意义,这就是其中之一。我们希望我们的程序与其他程序兼容,因此我们将坚持std::string
并创建一个类StringUtil_
(或其他东西)。
我会说,如果你坚持每堂课一个 util 是最好的。我会说所有类都有一个工具或一个类有多个工具是愚蠢的。
现在回头看这个问题,我想说 C# 扩展方法完全破坏了对实用程序类的需求。但并不是所有的语言都有这样一个天才的结构。
您还拥有 JavaScript,您可以在其中向现有对象添加一个新函数。
但我不确定是否真的有一种优雅的方法可以用像 C++ 这样的旧语言来解决这个问题......
好的 OO 代码有点难写,而且很难找到,因为写好的 OO 比写像样的函数式代码需要更多的时间/知识。
当你有预算时,你的老板并不总是很高兴看到你花了一整天的时间写一堆课程......
仅仅因为设计师想不出一个合适的地方来放置代码,就很容易将某样东西标记为实用程序。通常很少有真正的“实用程序”。
根据经验,我通常将代码保存在第一次使用它的包中,然后仅在以后发现其他地方确实需要它时才重构到更通用的地方。唯一的例外是如果我已经有一个执行类似/相关功能的包,并且代码最适合那里。
包含无状态静态方法的实用程序类可能很有用。这些通常很容易进行单元测试。
使用 Java 8,您可以在接口中使用静态方法……问题已解决。
大多数 Util 类都很糟糕,因为:
静态库与动态库有一些类比。
当我无法向类添加方法时(例如,Account
被 Jr. Developers 锁定以防止更改),我只需向我的 Utilities 类添加一些静态方法,如下所示:
public static int method01_Account(Object o, String... args) {
Account acc = (Account)o;
...
return acc.getInt();
}
实用程序类并不总是邪恶的。但它们应该只包含在广泛的功能中通用的方法。如果有一些方法只能在有限数量的类中使用,请考虑创建一个抽象类作为公共父类并将方法放入其中。