全局静态类和方法是否不好?

33

一般认为过度依赖全局变量是应该避免的。那使用静态类和方法是否等同呢?


为什么你会说“回到过去的日子”?你有没有理由认为这种说法现在不那么被人所反感了呢? - jalf
重复问题:https://dev59.com/xXRB5IYBdhLWcg3wAjJH和https://dev59.com/fHRB5IYBdhLWcg3wETxJ以及https://dev59.com/1UvSa4cB1Zd3GeqPhtvE等等。此外,它可能应该是社区Wiki页面。 - Abel
全局状态不好,但我们有单例之类的东西(它们是解决方法而不是真正的解决方案)的原因是它们至少依赖于惰性构造(在访问时初始化),这避免了初始化顺序问题。此外,它们是对象的事实意味着我们可以将面向对象编程技术应用于它们,如继承、使用多态等等。 - stinky472
这个问题似乎被过度编辑了。许多评论现在甚至没有意义。一些标签也是如此。这表明,当累积了十年的小样式编辑时,会发生什么。 - Nate T
10个回答

53

全局数据是不好的。但通过使用静态方法可以避免许多问题。

我借鉴了Rich Hickey的观点来解释这个问题:

在C#中,为了构建最可靠的系统,请使用静态方法和类,而不是全局数据。例如,如果您将数据对象传递给静态方法,并且该静态方法不访问任何静态数据,则可以确信,针对给定输入数据,该函数的输出始终相同。这是Erlang、Lisp、Clojure和每个其他函数式编程语言所采取的立场。

使用静态方法可以大大简化多线程编程,因为,如果正确编程,只有一个线程将同时访问给定数据集。这就是关键所在。全局数据很糟糕,因为它是可以被不知道哪个线程何时更改的状态。然而,静态方法允许非常干净的代码,可以按较小的增量测试。

我知道这可能会引起激烈争论,因为它与C#的面向对象思想背道而驰,但我发现使用越来越多的静态方法,我的代码越来越清晰、可靠。

这个视频比我更好地解释了这一点,但它展示了不可变数据和静态方法如何产生极其线程安全的代码。


让我更明确一些关于全局数据的问题。常量(或只读)全局数据比可变(读/写)全局数据不那么重要。因此,如果有意义拥有全局缓存数据,则使用全局数据!在某种程度上,使用数据库的每个应用程序都将具有这种情况,因为我们可以说所有SQL数据库都是一个大型的全局变量,保存数据。

因此,像我上面所说的那样做出一个笼统的陈述可能有点过分。相反,我们可以说,使用全局数据会引入许多问题,而使用本地数据则可以避免这些问题。

一些编程语言,如 Erlang,通过在单独的线程中缓存处理所有对该数据的请求来解决这个问题。这样,您就可以知道对该数据的所有请求和修改都是原子的,全局缓存也不会处于某种未知状态。


8
+1 表示同意“全局数据不好。但是通过使用静态方法可以避免许多问题。”这句话区分了静态数据和静态方法。 - decyclone
2
请不使用全局数据,您能详细说明如何在IIS服务器上避免浪费内存吗?每个请求都会产生数十个查找、映射和字典,这可能会消耗10-20 MB的连接。我认为“全局数据是不好的”这种笼统的说法会导致Web开发人员做出一些糟糕的设计选择。 - code4life
看起来没有人真正充分利用状态和无状态,而这在这里是核心。我同意 @code4life 的观点,笼统的陈述往往会误导人(尤其是当它们出现在第一句时!)。 - Marc
1
@code4life - 你说得对,有时确实需要全局数据。例如,我有一个图形渲染引擎,在其中缓存了数百兆字节的数据供多个线程使用。因此,也许我的总体陈述有些不准确。让我编辑一下我上面的评论。 - Timothy Baldridge
全局静态缓存与传递接口的缓存相比有哪些优势?后者使测试和调试更容易。前者能为我们做什么? - mg30rg
@TimothyBaldridge,您能否详细说明一下“例如,如果您将数据对象提交给静态方法,并且该静态方法不访问任何静态数据......那么您可以确信,在给定输入数据的情况下,函数的输出始终是相同的”这个说法?谢谢。 - Abdulkarim Kanaan

13

static并不一定意味着全局的。类和成员可以是static private,因此仅适用于特定的类。也就是说,过多使用public static成员而不是使用适当的方式传递数据(方法调用、回调等)通常是不良的设计。


6
static private 数据仍然是全局的,尽管它只对相关类可见。 - Jeff Sternal
1
-1 表示误解了私有静态数据是什么。请参见上面的评论。 - Timothy Baldridge
11
我很明白什么是private static。私有的静态成员不应该被全局访问(即从声明它的类以外的任何其他类)。当然,它的数据是全局的,但如果没有技巧,它就无法全局地访问 - Janick Bernet
6
但它仍然存在与全局数据相同的问题,即任何访问该数据的内容都会修改每个其他线程/方法调用使用的相同数据。这是全局或静态数据的真正问题,它破坏了代码的确定性本质,使得调试和维护更加困难,因为您无法知道哪种方法修改了代码。C#对象中的静态私有数据与C中的文件全局变量实际上是不同的。两者都有相同的缺点。 - Timothy Baldridge
5
你在这里关注的更多是并行数据访问问题,而不是静态成员问题。相同数据被不同线程使用的问题与静态或非静态数据无关。公共静态数据的问题在于它经常被错误地用于在类/对象之间传递某些信息,而不是将相应数据作为参数传递给方法。引入静态“注册表”会在该存储库上产生高耦合/依赖性。这就是我对公共静态存在的问题,这绝对不适用于私有静态。 - Janick Bernet
显示剩余2条评论

8

如果您试图在面向对象的开发中保持纯粹主义,那么静态方法可能并不适合这种模式。

然而,现实世界比理论更加混乱,静态方法通常是解决某些开发问题的非常有用的方式。适当使用,并适度使用。


3
可变的静态变量很糟糕,因为它们只是全局状态。我所知道的最好的讨论在这里,标题为“当不必要时应避免全局变量”
静态方法有几个缺点,通常会使它们不受欢迎-最大的缺点是它们不能多态地使用。

这完全取决于你对“全局”的定义。 - GalacticCowboy
final static变量有任何异常吗? - Dean J
2
@Dean J - 的确,我不是疯子。 :) 我会相应地更新我的答案。 - Jeff Sternal

3

除了其他所说的,final static变量是可以的;常量是好的。唯一的例外是当你应该将它们移到属性文件中以方便更改。


3
这个问题被标记为c#,所以我会假设你是指“const”而不是“final static”。 - Powerlord
2
这个问题也被标记为与语言无关 - 也许它是一个平局? - Jeff Sternal
给两个评论点赞;我仍然不能很快地在C#和Java之间切换。 - Dean J
在我的项目中,我甚至将常量存储在通过IConstantProvider接口访问的ConstantProvider类中。 - mg30rg

2
public class Foo
{
    private static int counter = 0;

    public static int getCounterValue()
    {
         return counter;
    }
    //...
    public Foo()
    {
        //other tasks
        counter++;
    }
}

在上面的代码中,您可以看到我们计算了创建了多少个Foo对象。这在许多情况下都是有用的。
static关键字不是全局的,它告诉您它在类级别上,这在各种情况下非常有用。因此,总之,类级别的东西是静态的,对象级别的东西不是静态的。

我可以从内心深处说出至少三种情况,当计数器不一定包含创建的Foo对象的实际数量时。 - mg30rg

2
首先,为什么旧的全局变量不好?因为它是可以在任何时间、任何地方访问的状态。很难追踪。
使用静态方法就没有这样的问题。
那就只剩下静态字段(变量)了。如果你在一个类中声明了一个公共的静态字段,那么它就真的是一个全局变量,并且会有问题。
但是将静态字段设为私有的,大部分问题就解决了。或者更好的做法是将问题限制在包含类中,这样就可以解决它们了。

2
静态方法被用于在Scala中实现特质。在C#中,扩展方法(它们是静态的)在部分中扮演了这个角色。正如DCI的支持者所说,这可以被看作是一种"更高阶的多态形式"
此外,静态方法可用于实现函数。这正是 F# 用来实现模块的方式。(以及VB.NET。) 函数对于(不出所料的)函数式编程非常有用。有时它们只是某些东西应该建模的方式(就像Math类中的“函数”一样)。同样,C#在这方面也做得很好

1

我认为全局变量的不好之处在于引入了全局状态的概念 - 变量可以在任何地方被操作,并且往往会在程序的远程区域引起意想不到的副作用。

静态数据与全局变量类似,因为它们引入了一种全局状态。但是,假设静态方法是无状态的,那么它们并不像全局变量那样糟糕。


0

并非完全如此。 静态实际上确定了何时,何地以及多久实例化某些内容,而不是谁可以访问它。


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