静态方法在什么情况下是一个好的实践?

47

我已阅读以下讨论:

如果可以是静态的,那么私有辅助方法是否应该是静态的?
如果类没有成员变量,是否应该使所有方法都是静态的?

总体而言,人们似乎会接受静态方法,但对其持怀疑态度,原因如下:

  1. 它们很难测试。
  2. 它们违反了面向对象的原则。(一位人士说,它们是函数,而不是方法。)

最可接受的静态方法是私有静态方法。但那么为什么静态方法存在呢?在什么情况下它们是首选的?


这个讨论也非常有用:Java中什么情况下不应该使用static关键字 https://dev59.com/YXI-5IYBdhLWcg3wm5vN - Mark Butler
11个回答

75

静态方法本身并不难测试。问题在于调用静态方法的其他代码很难测试,因为你无法替换静态方法。

我认为当静态方法是私有的或是“工具”方法(例如执行字符串转义)时,使用它们没什么问题。当你在测试中想要mock out或者以其他方式替换静态方法时,问题就出现了。工厂方法也可能有用,虽然依赖注入通常是更好的方法 - 这部分取决于您是否希望在测试中能够替换功能。

至于不符合“面向对象”的问题- 您在通常使用面向对象语言编写的所有内容都不必是“纯粹”的面向对象。有时候非面向对象的路线只是更实用,并且会导致更简单的代码。Eric Lippert在他的一篇非常好的博客文章中探讨了这个问题,但很遗憾我现在找不到它了。但是,在此帖子中有一条评论与此相关。它谈论的是扩展方法而不是静态方法,但原则是相同的。

扩展方法经常被批评为“不够面向对象”。这对我来说似乎是本末倒置。面向对象的目的是提供指导大型软件项目结构的准则,由一组不需要知道彼此工作内部细节就可以高效工作的人员编写。C#的目的是成为使我们的客户在我们的平台上能够提高生产力的有用的编程语言。显然,面向对象是有用和流行的,因此我们试图让在C#中以面向对象风格编程变得容易。但是,C#的目的不是“成为面向对象的语言”。我们根据它们是否对我们的客户有用来评估功能,而不是基于它们是否严格符合某些抽象。

学术理想是什么让一门语言成为面向对象的语言。我们欣然接受oo、函数式、过程式、命令式、声明式等任何思想,只要我们能够制作出一款一致且有用的产品,使我们的客户受益。


谢谢。解释得非常好。那么你认为实用方法是一个好的实践,对吗?但我不明白为什么人们会使用静态而不是“私有”或“实用”。基本上,我经常使用实用方法。 - Winston Chen
3
"静态方法"是指与类型本身而非类型的实例相关联的方法。在某些领域,这种方法很常见;在其他领域则比较少见。 - Jon Skeet
起初,我并不明白静态方法无法被替换的道理。经过一番研究后,我意识到在测试中很难甚至不可能用模拟对象来替代它们。 - Emanuele Bellini

22

我认为静态方法在它们是函数时绝对没问题,也就是说,它们不执行任何IO,没有任何内部状态,并且只使用它们的参数计算返回值。

如果一个静态方法改变它的参数状态,我也会把它归类到静态方法。不过,如果这样做过度,那么这个静态方法应该是主要操作参数类的实例方法。


7

想一想。在面向对象编程中,每个函数调用实际上看起来像这样:

method(object this, object arg1, object arg2) 这里的this是你正在调用的对象。它只不过是语法糖而已。此外,它允许你清晰地定义变量的作用域,因为你有对象变量等。

静态方法根本没有“this”参数。也就是说,你传入变量,可能会得到一个返回结果。首先,人们避免使用它的主要原因是你无法创建一个指向静态方法的接口(尚未),因此你无法模拟静态方法以进行测试。

其次,面向对象是过程函数等。在某些情况下,静态方法非常有意义,但它们总是可以转换为对象的方法。

请注意,如果没有hack,你不能删除this:

static void Main(string[] args)
{
}

启动应用程序的代码必须可调用而不需要对象引用。因此,它们为您提供了灵活性,是否选择在您的情况下使用它们将取决于您的要求。


1
你目前无法为静态方法创建接口。10年后,仍在等待... - Big_Bad_E

5
静态方法在大多数情况下都可以胜任,单例模式提供的灵活性过于强大。
例如,考虑一个简单的实用程序,如将原始值提高到幂 - 显然您永远不需要在其中进行任何多态性。原始值是静态类型的,数学运算是定义良好且不会改变的。这不像你将永远遇到的情况有两个不同的 实现并且没有办法在不重写所有客户端代码的情况下切换它们。

(讽刺结束)

现代JVM在加载接口的唯一实现时,非常擅长内联小调用。除非您已经对代码进行了剖析,并知道将实用程序分派到接口是一种开销,否则您没有理由不将实用方法转换为接口,以便在需要时可以进行变化。


3

我认为静态方法的明确用例是当你无法使它们动态,因为你无法修改类时。

这在JDK对象、来自外部库的所有对象以及原始类型中很常见。


3
另一个适合静态方法的好场景是实现工厂模式,其中允许以特定方式构造类的实例。
考虑Calendar类,它具有一组静态getInstance方法,每个方法返回使用所需的TimeZone和/或Locale或默认值预置的实例。

我不使用依赖注入或(经常)模拟对象进行测试,但实现工厂的静态方法会使模拟变得不可能,对吗? - Miserable Variable

2

我经常使用静态工厂方法来代替或与公共构造函数一起使用。

当我需要一个构造函数执行某些你不希望构造函数执行的操作时,我会这样做。例如从文件或数据库中加载设置。

这种方法还可以根据它们的功能命名“构造函数”,这在参数本身无法解释构造函数的情况下特别有用。

Sun公司在其

Class.forName("java.lang.Integer");

并且。
Boolean.valueOf("true");

1

Util类可以是静态的。就像上面某个人的示例中转义字符串一样。问题在于当这些utils类执行我们想要模拟的函数时。例如,Apache Commons中的FileUtils类 - 经常出现我想要模拟文件交互而不必使用真实文件的情况。如果这是一个实例类,那么这将很容易。


1

重构静态方法要容易得多。它会阻止对使耦合紧密的字段成员的不必要引用。同时,调用代码更容易理解,因为它明确地传递了与之交互的任何对象。


1

首先,你不能忽略静态方法,人们仍然使用它是有原因的。

  1. 一些设计模式基于静态方法,例如单例:

    Config.GetInstance();

  2. 辅助函数,比如说你有一个字节流,想要一个函数将其转换成十六进制数字的字符串。

静态方法有很多用途,但这并不意味着有些人滥用它(当人们滥用代码时,最好的选择是进行代码审查)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接