使用仅包含静态方法的*** Helper或*** Util类是AntiPattern


19

我经常遇到Java或任何类型的语言中的helper或util类。因此,我问自己这是否是某种反模式,而此类类的存在只是软件设计和体系结构中缺少一些缺失。

通常,这些类仅使用静态方法进行限制,该方法可以完成很多工作。但大多数情况下,确实是上下文相关的并且是全状态的。

我的问题是,您对此类静态辅助程序/ util类有何看法,因为其优势当然是仅使用类名进行快速调用。

在哪种抽象级别上,您将避免使用此类?

我认为关键字“ static”应该只允许在类(Java)的声明中使用,而不能用于方法。我认为以这种方式使用它,是在Java中结合过程式和OO-范式并避免滥用关键字的一种很好的选择,也是中间的选择。

由于答案而增加的内容:

起初,我认为能够组合不同的范例甚至在机器或vm编译的代码中使用运行时解释的脚本语言是完全合法的。

我的经验是,在项目的开发过程中,此类帮助器和utils或其他名称正在不断增长,并在最初被设计为模块化和灵活的代码库的每个被遗忘的角落使用。而且由于缺乏时间进行重构或重新考虑设计,随着时间的推移,您的情况只会变得更糟。

我认为static应该从Java中删除。尤其是在现在可以使用更复杂的功能语言元素的地方。



我认为如果助手不需要实例化一个类,那就很好。问题是当帮助程序实例化类的具体实现时,您将无法模拟该接口。如果帮手传递了接口或抽象类,我认为可以。
bobek

如果您声称要进行过程编程,那就很好。不是您声称要进行面向对象的编程。
斑点

1
@Spotted:由于声称您仅进行面向对象的编程并没有商业价值,因此请始终进行混合范例编程并完成。
RemcoGerlich

@RemcoGerlich确实如此。我要指出的是,答案主要是“哲学的”,与您所考虑的编程范例有关。
斑点

Answers:


20

好吧,Java缺少自由函数,因此您不得不将它们作为静态函数放入某种形式的类中。必要的解决方法永远不是反模式,尽管它可能缺乏优雅。

接下来,自由函数没有错。实际上,使用自由函数可以减少耦合,因为它们只能访问公共接口,而不能访问所有杂项细节。

当然,使用自由/静态功能不会以任何方式减轻可变共享状态(尤其是全局状态)的危险。


2
@TimothyTruckle它们是静态的,或者它们是多余的this,并且需要适当地实例化某些内容
Caleth

6
@TimothyTruckle因为它是Java。其他语言可以具有自由函数,而不必将它们放入类中只是为了声明它不是实例方法而产生的开销。在某些时候,您必须紧密耦合。不管是好还是不好完全取决于什么有问题的功能
nvoigt

5
@TimothyTruckle当然,保持状态将是“不好”的主要原因。
nvoigt

2
@nvoigt,我认为明确指出这一点将大大改善此答案。有状态的静态变量是有害的;无国籍的人很好。真的很简单。
David Arno

3
@TimothyTruckle这不是紧密的耦合。您正在描述松散耦合。在这种情况下,紧密耦合意味着某种程度的相互依赖。单向依赖项不满足该要求。
JimmyJames

18

如果静态实用程序或辅助函数遵循某些准则,则它们不是反模式:

  1. 他们应该没有副作用

  2. 它们用于特定于应用程序的行为,这些行为不属于这些函数所运行的一个或多个类。

  3. 他们不需要任何共享状态

常见用例:

  • 以特定于应用程序的方式格式化日期
  • 以一种类型为输入并返回另一种类型的函数。

例如,在我处理过的一个应用程序中,用户保留其活动的日记帐分录。他们可以指定后续日期并下载事件提醒。我们创建了一个静态实用程序类来获取日记条目并返回.ics文件的原始文本。

它不需要任何共享状态。它不会改变任何状态,并且创建iCal事件当然是特定于应用程序的,并且不属于日记帐分录类。

如果静态函数或实用工具类具有副作用或需要共享状态,则建议重新评估该代码,因为它确实引入了耦合,这对于单元测试而言可能很难模拟。


4

这将取决于您的方法。与经典思维方式(包括您所使用的许多网站套件)相反,许多应用程序的功能更强大。

以此思路,工作者类上将有许多实用程序方法可以作为静态方法串在一起。它们被喂食/使用得更靠近仅保存数据并传递的普通对象。

这是一种有效的方法,可以很好地工作,尤其是在大规模的情况下。


“可以很好地工作,尤其是在大规模情况下”。随着项目的发展,静态访问导致的紧密耦合将很快成为您的障碍。
蒂莫西·卡特尔

@TimothyTruckle您能解释一下Agent.Save(obj)如何比obj.Save()更紧密地耦合吗?即使对于类实例的静态方法,Agent也具有获取DI / IoC引用的能力。供参考,Stack Exchange本身就是用这种思维方式编写的,并且其伸缩性比其他ASP.Net应用程序更好,并且资源比我所知道的其他任何应用程序都要少。
Tracker1

“您能解释一下Agent.Save(obj)如何比obj.Save()更紧密地耦合吗?” 这是错误的例子。正确的例子是Agent.Save(obj)对比agentInstance.Save(obj)。使用后者,您可以注入 agentInstance使用代码。因此,您也可以注入任何子类Agent从而允许重新使用带有不同“类型”的使用代码,Agent而无需重新编译。这是低耦合
蒂莫西·特拉

我认为这实际上归结为两件事。是否需要状态,状态是否需要灵活,是否可以具有全局/实例状态?这取决于每个开发人员/架构师。我宁愿使用更简单的代码库,也不愿为场景添加复杂性,因为这会使整体上的所有内容变得更加难以理解。我对node / js钟爱的一件事是,我可以编写简单/干净的代码,并且仍然可以注入测试而无需编写专门针对DI / IoC的代码。
Tracker1

2

我个人认为,关于此类帮助程序类的唯一重要部分是将它们私有化。除此之外-严格来说,这是一个好主意(少量应用)。

我对此的思考-如果是在实现一个类(或函数)时-这样的事情是有帮助的,并使该实现更清晰,那怎么可能不好呢?通常,定义此类私有帮助器类以允许与其他算法集成并使用依赖于特定形状数据的其他算法至关重要。

是否将其标记为“帮助者”只是个人品味的小问题,但它暗示它有助于实施,并且对更广泛的受众毫无用处。如果那是有意义的,那就去吧!


1
公共实用程序类可能仍然有用。主要示例:java.lang.Math
阿蒙(Amon)

1

static辅助类的普及是基于一种误解。仅仅因为我们仅使用static方法将类称为“实用程序类”,并不意味着它不允许在POJO中编写常见行为。

static 辅助类是反模式的,原因有以下三个:

  1. 对该辅助方法的静态访问隐藏了依赖项。如果这些“实用程序类”是POJO,则可以将它们作为构造函数参数注入到一个依赖类中,这将使依赖对于任何依赖类的用户都是显而易见的。

  2. 静态访问此辅助方法会导致紧密耦合。这意味着使用辅助方法的代码很难重用,并且(很不利)要进行测试。

  3. 特别是如果它们保持状态,那么它们仅仅是全局变量。希望没有人认为全局变量有好处...

静态帮助程序类是STUPID代码反模式的一部分。


全局状态与问题无关– max630

OP写道:

但大多数情况下,确实是上下文相关的并且是全状态的。

静态全状态构造全局状态。

任何类型的代码都可以使用它。– max630

是的,原因。几乎所有应用程序都需要某种全局状态

但是 全局状态 != 全局变量

您可以通过依赖项注入使用OO技术创建全局状态,但是您无法避免使用状态完整的静态结构(位于底部的全局变量)来生成 全局状态


2
全局状态与问题无关,任何类型的代码都可以使用它。
max630

@ max630,请参阅我的更新
Timothy Truckle '18

1
您可以使用无静态功能而无需耦合。您传递Func<Result, Args>对象,这些对象在其他地方实例化。
卡莱斯(Caleth)'18

1
1)我一般认为在适当的地方隐藏实现是一件好事。2)将所有内容作为自变量/依赖关系注入还会创建一种设计时耦合,当您发现自己不得不花费大量时间来更改长的依赖链中的签名时,您会感觉到这一点。就像让每个函数都需要您的Logger或其他横切关注点一样,争吵……(但是,它会使低级测试变得更加困难)3)当然,静态函数应该是无状态的。
Alex

1
@Alex然后让我反过来说:单元是具有相同更改理由的任何代码(包括静态引用的“实用程序”方法)。本单元内的所有内容都是实现细节。其他都是依赖项,并且该单元不应对此具有静态访问权限。
蒂莫西·特拉
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.