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


95

我看到了这个线

如果“实用程序”类是邪恶的,那么我应将通用代码放在哪里?

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

可以说我有一个深度类很多的领域模型。我需要能够xml-ify实例。我是否在父对象上创建toXml方法?我是否要创建MyDomainXmlUtility.toXml帮助器类?在这种情况下,业务需求跨越了整个域模型-它真的属于实例方法吗?如果应用程序的xml功能上有一堆辅助方法,那该怎么办?


27
贬低“邪恶”就是邪恶!
马修·洛克

1
@matthew我保留了我的问题基于该帖子的条款...;)
hvgotcodes 2010年

实用程序类不是一个好主意,因为单例是同样的原因。
CurtainDog

1
关于是否拥有toXML方法的争论集中在富域模型与贫血症域模型之间。codeflow.blogspot.com/2007/05/anemic-vs-rich-domain-models.html
James P.

@ james,toXML只是一个示例...到处使用的某些正则表达式功能又如何呢?就像,您需要对整个域模型中的字符串进行一些处理,但是由于使用了一个超类(在Java中)的另一个重要问题,您无法使用子类化
hvgotcodes 2011年

Answers:


127

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

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

确实,您需要做的是考虑设计并确定放置方法最有意义的位置。通常,它作为类内部的操作。但是,有时确实是作为实用程序类。但是,当您使用实用程序类时,不要只是将随机方法放入其中,而应根据目的和功能组织这些方法。


在良好的联系与本文有关检查对实体OO原则工具/辅助类以及readability.com/articles/rk1vvcqy
melaos

8
如果语言不提供类以外的任何名称空间机制,则您别无选择,只能滥用名称空间可以提供的类。在C ++中,您可以将独立于其他函数的独立函数放入命名空间中。在Java中,必须将其作为静态(类)成员放入类中。
Unslander Monica 2015年

1
从OOP的角度来看,作为方法分组可能是典型的。但是,OOP很少是解决方案(除例外是高级架构和窗口小部件工具包,这些实例实际上是通过继承来重用令人难以置信的代码语义的实例之一),通常,方法会增加耦合和依赖性。一个简单的例子:如果您向类提供打印机/扫描仪方法作为方法,则将该类耦合到打印机/扫描仪库。另外,您将可能的实现方式的数量固定为一个。除非您抛出接口并进一步增加依赖关系。
乔苏

另一方面,我同意您的观点“按目的和功能分组”。让我们再次回顾打印机/扫描仪示例,并从一组通用的类开始,这些类是为通用目的而设计的。您可能需要编写一些调试代码并为您的类设计文本表示形式。您可以在一个文件中实现调试打印机,文件取决于所有这些类和打印机库。非调试代码不会因该实现的依赖而负担。
乔苏

1
Util和Helper类是此类约束的不幸产物,那些不了解OOP或不了解问题域的人仍在继续编写过程代码。
bilelovitch

56

我认为,普遍共识是实用程序类本身并不是邪恶的。您只需要谨慎使用它们:

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

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

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

  • 对于Java 8及更高版本,接口中的“默认方法”可能比实用程序类更好。(例如,请参阅带有默认方法的接口与Java 8中的Abstract类。)


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

“如果猪可以飞,我应该带雨伞吗?”

在上面的问题,我没有真正说猪都能飞...或者说我用的命题,他们同意可以飞。

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


16

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

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

从“ 干净代码”的角度来看,实用程序类违反了“单一职责”和“开放-封闭原则”。他们有很多改变的理由,并且设计上是不可扩展的。它们实际上仅应在重构过程中作为中间组件存在。


2
我将其称为一个实际问题,因为例如在Java中,您无法创建不是类中方法的任何函数。在这种语言中,“没有变量且只有静态方法的类”是代表实用程序功能名称空间的惯用法……当面对Java中的此类时,恕我直言是无效的,而且说一个类毫无意义。 ,即使class使用了关键字。它像命名空间一样
嘎嘎叫

2
抱歉,直言不讳,但是“ OOP系统哲学问题中的无状态静态方法奇怪”是极度误导的。如果您可以避免状态,那就太好了,因为这样可以轻松编写正确的代码。当我不得不写时,它就破了,x.equals(y)因为1)事实上,这种状态不应该修改任何状态,这一事实并未得到传达(并且我不能依靠它不知道实现情况)2)我从来没有打算将x“相比于y3)我不想考虑“身份” xy仅仅对它们的价值感兴趣。
乔苏

4
现实中,实际上大多数功能最好用静态,无状态,数学函数表示。Math.PI是应该突变的对象吗?我真的必须实例化一个实现IAbstractRealOperation的AbstractSineCalculator来获取数字的正弦值吗?
乔苏

1
@JoSo我完全同意无状态和直接计算的价值。天真的OO在哲学上反对这一点,我认为这使它存在严重缺陷。它鼓励对不需要的事物进行抽象(如您所演示的),并且无法为实际计算提供有意义的抽象。但是,使用OO语言的一种平衡方法倾向于不变性和无状态性,因为它们促进了模块化和可维护性。
阿兰·奥德亚

2
@ AlainO'Dea:我意识到我误解了您的评论,因为它反对无状态功能。我为自己的口气表示歉意-我目前正在参与我的第一个Java项目(在我十年编程的大部分时间里避免使用OOP之后),并且被埋在状态和含义不清楚的抽象事物之下。我只需要一些新鲜空气:-)。
乔所以

8

我想它开始变得邪恶

1)太大(在这种情况下,请将它们分为有意义的类别)。
2)存在不应为静态方法的方法

但是,只要不满足这些条件,我认为它们非常有用。


“存在不应为静态方法的方法。” 这怎么可能?
Koray Tugay 2015年

@KorayTugay考虑非静态Widget.compareTo(Widget widget)与静态WidgetUtils.compare(Widget widgetOne, Widget widgetTwo)。比较是不应静态完成的示例。
ndm13

5

经验法则

您可以从两个角度看待这个问题:

  • 总体*Util方法通常是不良代码设计或惰性命名约定的暗示。
  • 它是可重用的跨域无状态功能的合法设计解决方案。请注意,对于几乎所有常见问题,都有现有的解决方案。

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

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

在此处输入图片说明


示例2.正确使用util类/模块。在util不找其他团队成员的情况下撰写自己的作品

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

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

在此处输入图片说明


1
我看到的是一位超级粉丝;)很棒的应用程序。
Sridhar Sarnobat,

3

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

话虽如此,我很懒。有时,您只需要完成工作,脑子一片空白..那时候“实用”类开始普及。


3

现在回头看这个问题,我想说C#扩展方法完全破坏了对实用程序类的需求。但是,并非所有语言都具有如此精巧的构造。

您还可以使用JavaScript,在其中只需向现有对象添加新功能即可。

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

好的OO代码有点很难编写,而且很难找到,因为编写好OO代码比编写体面的功能代码需要更多的时间/知识。

而且当您预算有限时,您的老板并不总是很高兴看到您花了一整天写了一堆课...


3

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

尽管实用程序类可能会在某些方面违反OO主体,但它们并不总是不好的。

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

stl c ++(截至目前)不直接支持此功能。

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

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

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

我要说的是,如果您每堂课只使用一个工具,那是最好的。我想说对所有类都使用一个实用程序或对一个类使用多个实用程序是很愚蠢的。


2

仅仅因为设计者无法想到放置代码的地方,就很容易为某实用程序加上商标。真正的“实用程序”通常很少。

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


2

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



1

大多数Util类很糟糕,因为:

  1. 它们扩大了方法的范围。他们将代码公开,否则将是私有的。如果util方法由多个调用者在单独的类中使用并且是稳定的(即不需要更新),那么我认为最好将私有帮助器方法复制并粘贴到调用类中。将其公开为API后,您将很难理解jar组件的公共入口点(您维护的树结构称为层次结构,每个方法只有一个父级。这在精神上更容易分离为组件,而当您有从多个父方法调用的方法)。
  2. 它们导致无效代码。随着时间的流逝,随着应用程序的发展,未使用的方法变得不可用,最终导致未使用的代码污染了代码库。如果它仍然是私有的,则编译器会告诉您该方法尚未使用,您可以删除它(最好的代码根本没有代码)。一旦将这种方法设为非私有,您的计算机将无能为力,以帮助您删除未使用的代码。对于所有计算机已知的信息,可以从其他jar文件中调用它。

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


0

当我无法向类中添加方法(例如,Account被Jr. Developers禁止更改)时,我仅向Utilities类中添加了一些静态方法,如下所示:

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

1
看来您对我来说是Jr。.P:这样做的非邪恶方法是礼貌地要求您的“ Jr开发人员”解锁Account文件。或者,最好使用非锁定源控制系统。
2013年

1
我认为他的意思是该Account文件已被锁定,因此开发人员无法对其进行更改。作为一名小型开发人员,要解决这个问题,他的做法如上所述。也就是说,最好只是获得对该文件的写访问权并正确执行它。
Doc

加上+1的原因是,当没人能提供提供答案的完整上下文时,
反对投票

0

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.