实用程序类是邪恶的吗? [关闭]

Posted

技术标签:

【中文标题】实用程序类是邪恶的吗? [关闭]【英文标题】:Are utility classes evil? [closed] 【发布时间】:2011-03-21 09:09:31 【问题描述】:

我看到了这个问题:If a "Utilities" class is evil, where do I put my generic code?

我想,为什么实用程序类是邪恶的?

假设我有一个包含几十个类的领域模型。我需要能够 xml-ify 实例。我是否在父级上创建一个 toXml 方法?我要创建一个 MyDomainXmlUtility.toXml 辅助类吗?这是业务需求跨越整个领域模型的情况——它真的属于实例方法吗?如果应用的 XML 功能上有一堆辅助方法呢?

【问题讨论】:

贬低“恶”这个词就是恶! @matthew 我保留了我的问题所依据的帖子的条款...;) 实用类是个坏主意,原因与单例相同。 关于是否拥有 toXML 方法的争论主要集中在富域模型与贫乏域模型上。 codeflow.blogspot.com/2007/05/anemic-vs-rich-domain-models.html @james,toXML 只是一个例子……那些到处都在使用的正则表达式功能呢?就像,您需要对域模型中的字符串做一些事情,但由于另一个使用您的超类(在 java 中)的首要问题,您不能使用子类化 【参考方案1】:

实用程序类并不完全是邪恶的,但它们可能违反构成良好面向对象设计的原则。在一个好的面向对象设计中,大多数类应该代表一个事物及其所有属性和操作。如果你在操作一个事物,那个方法应该是那个事物的成员。

但是,有时您可以使用实用程序类将多个方法组合在一起——例如java.util.Collections 类,它提供了许多可用于任何 Java 集合的实用程序。这些并不特定于一种特定类型的 Collection,而是实现可用于任何 Collection 的算法。

确实,您需要做的是考虑您的设计并确定最适合放置方法的位置。通常,它是作为类内部的操作。但是,有时,它确实是一个实用程序类。但是,当您使用实用程序类时,不要只是将随机方法放入其中,而是按用途和功能组织方法。

【讨论】:

如果该语言不提供除类以外的任何命名空间机制,那么您别无选择,只能滥用命名空间可以使用的类。在 C++ 中,您可以将与其他函数无关的独立函数放入命名空间。在Java中,你必须把它作为一个静态(类)成员放入一个类中。 从 OOP 的角度来看,作为方法分组可能是规范的。然而,OOP 很少是解决方案(例外情况是高级架构和小部件工具包作为通过继承重用代码的令人难以置信的破坏语义实际上适合的实例之一)并且通常方法会增加耦合和依赖关系。简单的示例:如果您将打印机/扫描仪方法作为方法提供给您的类,则将该类耦合到打印机/扫描仪库。另外,您将可能实现的数量固定为有效的一个。除非你扔接口,进一步增加依赖。 另一方面,我同意您的观点“按目的和功能分组”。让我们再次回顾一下打印机/扫描仪的例子,从一组协调的类开始,为一个共同的目的而设计。您可能想要编写一些调试代码并为您的类设计文本表示。您可以在 单个 文件中实现调试打印机,该文件依赖于所有这些类和打印机库。非调试代码不会被这个实现的依赖所累。 Util 和 Helper 类是这种约束的不幸产物,那些不了解 OOP 或不了解问题域的人继续编写程序代码。【参考方案2】:

我认为普遍的共识是实用程序类本身并不邪恶。你只需要明智地使用它们:

将静态实用程序方法设计为通用且可重用。确保它们是无国籍的;即没有静态变量。

如果您有很多实用方法,请将它们划分为类,以便开发人员轻松找到它们。

如果域类中的静态或实例方法会是更好的解决方案,请不要使用实用程序类。例如,考虑抽象基类或可实例化辅助类中的方法是否是更好的解决方案。

从 Java 8 开始,接口中的“默认方法”可能比实用程序类更好。 (例如,请参阅When to use: Java 8+ interface default method, vs. abstract method。)


查看此问题的另一种方法是观察在引用的问题中,“如果实用程序类是“邪恶”” 是一个稻草人论点。就像我在问:

“如果猪会飞,我应该带伞吗?”。

在上面的问题中,我实际上并不是说猪会飞......或者我同意他们可以1的命题。

典型的“xyz 是邪恶的” 陈述是一种修辞手段,旨在通过提出极端观点来让你思考。它们很少(如果有的话)用作文字事实的陈述。


1 - 你不应该把这个稻草人问题解释为你是否应该始终随身携带雨伞的建议。

【讨论】:

【参考方案3】:

实用程序类存在问题,因为它们未能将职责与支持它们的数据进行分组。

但是它们非常有用,我一直将它们构建为永久结构或在更彻底的重构过程中作为垫脚石。

从Clean Code 角度看,实用程序类违反了单一职责和开闭原则。它们有很多改变的理由,并且在设计上是不可扩展的。它们真的应该只在重构过程中作为中间产物存在。

【讨论】:

我认为这是一个实际问题,因为例如在 Java 中,您不能创建任何不是类中方法的函数。在这样的语言中,“没有变量且只有静态方法的类”是一个代表实用函数命名空间的习语......当在 Java 中面对这样一个类时,恕我直言,谈论一个类是无效和毫无意义的, 即使使用了 class 关键字。它像命名空间一样嘎嘎作响,它像命名空间一样行走——它是一个...... 我很抱歉直言不讳,但是“无状态静态方法 [...] OOP 系统中的古怪方法 [...] 哲学问题”是非常错误的。如果您可以避免状态,那就太好了,因为这样可以很容易地编写正确的代码。当我不得不写x.equals(y) 时它就坏了,因为 1)这确实不应该修改任何状态的事实没有被传达(而且我不能依赖它不知道实现) 2)我从来没有打算把xy 相比处于“受青睐”或“采取行动”的位置 3) 我不想考虑 xy 的“身份”,但我只是对它们的价值观感兴趣。 实际上,现实世界中的大多数功能最好用静态、无状态的数学函数表示。 Math.PI 是一个应该被变异的对象吗?我真的必须实例化一个实现 IAbstractRealOperation 的 AbstractSineCalculator 来获取数字的正弦吗? @JoSo 我完全同意无状态和直接计算的价值。 Naive OO 在哲学上反对这一点,我认为这使得它存在严重缺陷。它鼓励对不需要它的事物进行抽象(如您所展示的),并且无法为实际计算提供有意义的抽象。然而,使用 OO 语言的平衡方法倾向于不变性和无状态,因为它们促进了模块化和可维护性。 @AlainO'Dea:我意识到我将您的评论误读为反对无状态功能。我为我的语气道歉——我目前正在参与我的第一个 Java 项目(在我 10 年编程的大部分时间里我避免了 OOP 之后)并且被埋在状态和含义不明确的抽象事物层之下。我只需要一些新鲜空气:-)。【参考方案4】:

我想当它开始变得邪恶时

1) 太大了(在这种情况下,只需将它们分组到有意义的类别中)。 2) 存在不应该是静态方法的方法

但只要不满足这些条件,我觉得还是很有用的。

【讨论】:

"存在不应该是静态方法的方法。"这怎么可能? @KorayTugay 考虑非静态Widget.compareTo(Widget widget) 与静态WidgetUtils.compare(Widget widgetOne, Widget widgetTwo)。比较是不应静态进行的示例。【参考方案5】:

经验法则

你可以从两个角度来看这个问题:

总体而言,*Util 方法通常表明代码设计不佳或命名约定不规范。 它是可重用跨域无状态功能的合法设计解决方案。请注意,几乎所有常见问题都有现有的解决方案。

示例 1. 正确使用 util 类/模块。外部库示例

假设您正在编写管理贷款和信用卡的应用程序。这些模块中的数据通过json 格式的Web 服务公开。 从理论上讲,您可以手动将对象转换为将在json 中的字符串,但这会重新发明***。正确的解决方案是在两个模块中包含用于将 java 对象转换为所需格式的外部库。 (在我展示的示例图像中gson)


示例 2. 正确使用 util 类/模块。写你自己的util 没有任何借口给其他团队成员

作为一个用例假设我们需要在应用程序的两个模块中执行一些计算,但它们都需要知道波兰何时有公共假期。理论上,您可以在模块内进行这些计算,但最好将此功能提取到单独的模块中。

这是一个很小但很重要的细节。你写的类/模块不是HolidayUtil,而是PolishHolidayCalculator。从功能上讲,它是一个util 类,但我们设法避免了通用词。

【讨论】:

我看到的一个 yEd 粉丝 ;) 很棒的应用程序。【参考方案6】:

实用程序类很糟糕,因为它们意味着您懒得为该类想出更好的名称:)

话虽如此,我很懒惰。有时您只需要完成工作而大脑一片空白……这就是“实用程序”课程开始出现的时候。

【讨论】:

【参考方案7】:

现在回头看这个问题,我想说 C# 扩展方法完全破坏了对实用程序类的需求。但并非所有语言都有这样一个天才的结构。

您还拥有 javascript,您可以在其中向现有对象添加一个新函数。

但我不确定是否真的有一种优雅的方法可以用像 C++ 这样的旧语言来解决这个问题...

好的 OO 代码有点难写,也很难找到,因为写好的 OO 比写好的函数式代码需要更多的时间/知识。

当你的预算有限时,你的老板并不总是很高兴看到你一整天都在写一堆课程......

【讨论】:

【参考方案8】:

我不完全同意实用程序类是邪恶的。

虽然实用程序类可能在某些方面违反 OO 原则,但它们并不总是坏事。

例如,假设您需要一个函数来清除与值 x 匹配的所有子字符串的字符串。

STL C++(截至目前)不直接支持这一点。

您可以创建std::string 的多态扩展。

但问题是,你真的希望你在项目中使用的每一个字符串都成为你的扩展字符串类吗?

有时 OO 并不真正有意义,这就是其中之一。我们希望我们的程序与其他程序兼容,因此我们将坚持使用std::string 并创建一个类StringUtil_(或其他东西)。

我会说最好每个班级都使用一个 util。我会说所有类都有一个 utils 或一个类有多个 utils 是愚蠢的。

【讨论】:

【参考方案9】:

仅仅因为设计师想不出合适的位置来放置代码,就很容易将某样东西标记为实用程序。通常很少有真正的“实用程序”。

根据经验,我通常将代码保存在第一次使用它的包中,然后仅在以后发现其他地方确实需要它时才重构到更通用的位置。唯一的例外是如果我已经有一个执行类似/相关功能的包,并且代码最适合那里。

【讨论】:

【参考方案10】:

包含无状态静态方法的实用程序类可能很有用。这些通常很容易进行单元测试。

【讨论】:

【参考方案11】:

使用 Java 8,您可以在接口中使用静态方法...问题已解决。

【讨论】:

没有解决***.com/a/3340037/2859065中提到的问题 这与将它们放在一个类中并导入有什么不同?【参考方案12】:

大多数 Util 类都不好,因为:

    它们拓宽了方法的范围。它们将原本私有的代码公开。如果 util 方法被不同类中的多个调用者需要并且是稳定的(即不需要更新),我认为最好将私有帮助方法复制并粘贴到调用类中。一旦将其作为 API 公开,您就更难理解 jar 组件的公共入口点是什么(您维护一个称为层次结构的树结构,每个方法都有一个父级。这更容易在心理上分离成组件,当您有从多个父方法调用的方法)。 它们会导致死代码。随着时间的推移,随着应用程序的发展,Util 方法会变得未使用,最终导致未使用的代码污染了您的代码库。如果它仍然是私有的,您的编译器会告诉您该方法未使用,您可以将其删除(最好的代码是根本没有代码)。一旦您将这种方法设为非私有,您的计算机将无法帮助您删除未使用的代码。它可能是从一个不同的 jar 文件调用的,所有计算机都知道。

静态库与动态库有一些类比。

【讨论】:

【参考方案13】:

当我无法向类添加方法时(例如,Account 被 Jr. Developers 锁定以防止更改),我只需向我的 Utilities 类添加一些静态方法,如下所示:

public static int method01_Account(Object o, String... args) 
    Account acc = (Account)o;
    ...
    return acc.getInt();
  

【讨论】:

看起来你对我来说是 Jr.. :P 做到这一点的非邪恶方法是礼貌地要求你的“Jr Developer”解锁Account 文件。或者更好的是,使用非锁定源代码控制系统。 我认为他的意思是 Account 文件已被锁定,因此 Jr. Developers 无法对其进行更改。作为一名初级开发人员,为了解决这个问题,他按照上面的方法做了。也就是说,最好只获得对文件的写访问权限并正确执行。 为此添加 +1 是因为当没有人了解您提供此答案的全部背景时,投票似乎很苛刻【参考方案14】:

实用程序类并不总是邪恶的。但它们应该只包含在广泛的功能中通用的方法。如果某些方法只能在有限数量的类中使用,请考虑创建一个抽象类作为公共父类并将方法放入其中。

【讨论】:

以上是关于实用程序类是邪恶的吗? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

函数指针是邪恶的吗? [关闭]

AVL 树是邪恶的吗? [关闭]

数据库触发器是邪恶的吗? [关闭]

深度克隆实用程序推荐 [关闭]

深度克隆实用程序推荐 [关闭]

是否有任何实用程序可以帮助我重构 CSS [关闭]