静态类和单例模式的区别是什么?

2049

静态类和单例模式在实际使用上有什么区别?

两者都可以在不实例化的情况下调用,并且都只提供一个“实例”,但它们都不是线程安全的。除此之外,还有其他区别吗?


4
根据语言实现和您的使用模式,单例模式可能会因每次调用getInstance()方法时的开销而变得不太高效(尽管在大多数情况下都无关紧要)。 - too much php
5
已经有很多答案了。实际上它是一个“单例”对象,其中的“静态”方法只是函数,一种非面向对象的实体。 - fastcodejava
4
取决于具体实现方式。http://csharpindepth.com/Articles/General/Singleton.aspx - VJAI
4
当您想允许第三方提供类的实现时,需要使用工厂模式。在这种情况下,通常也需要使用工厂模式。详情请参见http://agiletribe.wordpress.com/2013/10/08/dont-abuse-singleton-pattern/。 - AgilePro
有趣的链接:https://dev59.com/QHI_5IYBdhLWcg3wK_zE - The Student
显示剩余3条评论
42个回答

1425

你为什么认为单例模式或静态方法不是线程安全的?通常,两者都应该实现为线程安全。

单例模式和一堆静态方法之间的主要区别在于,单例模式可以实现接口(或派生自有用的基类,尽管这在我的经验中比较少见),因此您可以将单例模式像“只是另一个”实现一样传递。


35
如果你喜欢的话,它们都不是天生线程安全的,你需要让它们成为线程安全的,两者都一样,所以没有区别。 - Jorge Córdoba
128
除了不可变类型以外,你能举一个本质上是线程安全的例子吗? - Jon Skeet
26
针对 Skeet 的观点,人们说单例不是线程安全的,是指单例在大多数情况下被不必要地在多个线程之间共享,而栈对象只在需要共享时才会被共享,这意味着你无需进行不必要的同步。 - Iraimbilanja
51
假设单例模式实现了一个名为 Foo 的接口,你有一个方法需要接受一个 Foo 类型的参数。在这种情况下,调用者可以选择使用单例模式作为实现 - 或者他们可以使用不同的实现。该方法与单例模式解耦了。而如果类只是包含静态方法,则每个想要调用这些方法的代码都与该类紧密耦合,因为它需要指定包含静态方法的类。 - Jon Skeet
10
根据单例设计模式,如果一个类允许创建多个实例,则它不是一个单例。无论工厂如何处理,我认为都是这样的。 - Jon Skeet
显示剩余14条评论

538

这个问题的真正答案来自Jon Skeet,在另一个论坛上

单例模式允许访问单个创建的实例 - 该实例(或者说是对该实例的引用)可以作为参数传递给其他方法,并被视为普通对象。

静态类只允许静态方法。


96
如果你可以通过调用静态的getInstance()方法从任何地方访问相同的实例,为什么还要将Singleton作为参数传递呢? - Henrique Ordine
30
那么它可以适配现有代码并提供一个接口? - user1115652
6
他们在谈论静态类,而不是带有静态方法的类。静态类无法实例化。然而,如果您传递一个包含静态方法的(非静态)类的实例,则无法在实例上调用静态方法。 - Goran
6
什么是静态类?至少在Java语言中,这样的东西并不存在。 - Henrique Ordine
20
起初我对你的措辞感到非常困惑。你说“你不能在实例上调用静态方法”。我理解为“如果你有一个实例化对象的引用,你不能调用它可能拥有的任何静态方法。” 当然,这是不正确的。经过几次阅读后,我认为你的意思是“在静态方法中,你无法访问类中的非静态对象”,这是正确的。希望澄清一下,如果有新手遇到这些概念并阅读了你的评论。 - Andrew Steitz
显示剩余8条评论

398
  1. 单例对象存储在中,但静态对象存储在中。
  2. 我们可以克隆(如果设计人员未禁止),单例对象,但不能克隆静态类对象。
  3. 单例类遵循面向对象原则(OOP),而静态类不遵循。
  4. 我们可以使用Singleton类实现接口,但类的静态方法(或例如C#的静态类)不能实现。

142
第二个陈述是错误的。我们无法克隆单例对象。单例实现必须拒绝此操作。如果你真的可以克隆单例,那么它就不是单例了。 - Alexander Yancharuk
31
这个答案对于Java来说是不正确的:单例模式和静态变量都不使用栈。 - AgilePro
88
#1 不重要。 #2 描述了一个有缺陷的实现。 #3 完全不能被辩解。 - Casey
37
静态对象如何存储在堆栈中?当您调用方法时,将创建新的堆栈帧来存储该方法的局部变量,当方法返回时,此堆栈帧将被删除,并且这些局部变量将会丢失。虽然堆栈操作速度较快,但不适合存储静态对象。 - mike_m
35
我不理解这个帖子的点赞数。1)为什么Singleton必须存储在堆栈中?在像C#或Java这样的托管语言中,数据存储在托管堆中,除了本地方法变量/参数之外。2)如果可以克隆它,则它不是一个正确实现的单例模式。3)Singleton被称为面向对象编程中的反模式;即,如果可能的话应该避免使用它。4)这是唯一正确的事情。 - vgru
显示剩余21条评论

193

单例模式比静态类具有几个优点。首先,单例可以扩展类和实现接口,而静态类不能(它可以扩展类,但不会继承它们的实例成员)。单例可以懒惰地或异步地初始化,而静态类通常在第一次加载时初始化,可能导致类加载器问题。然而,最重要的优点是,单例可以进行多态处理,而不会迫使其用户假设只有一个实例。


12
支持好的、实用的观点。 单例模式通常被过度使用,但在某些情况下它很合适。 请参见:http://agiletribe.wordpress.com/2013/10/08/dont-abuse-singleton-pattern/ - AgilePro
7
你关于多态性的优点说得对。这是最重要的一点。 - Ahmad
嵌套静态类可以实现接口。尝试编写代码,它会起作用。我可以编译代码而不出现任何错误。 - nanosoft

94

静态类不适用于需要状态的任何内容。它非常适合将一堆函数放在一起,例如项目中的Math(或Utils)。因此,类名只是给我们一个提示,告诉我们可以在哪里找到这些函数,仅此而已。

单例模式是我最喜欢的模式,我使用它来管理单个点上的某些事物。它比静态类更灵活,可以维护自己的状态。它可以实现接口、继承其他类并允许继承。

我选择静态单例之间的规则:

如果有一堆应该保持在一起的函数,则选择静态。 任何其他需要对某些资源进行单独访问的内容,都可以实现为单例


21
静态类为什么不能执行需要保存状态的操作? - Trisped
14
@Trisped:你既无法精确控制初始化,也无法控制终止。 - Xaqron
9
当你说"Singleton是我最喜欢的设计模式"时,我就跟不上了。Singleton是一个非常尖锐的角落,它既应该被视为反模式,也应该被视为模式。类可以很好地拥有静态状态,这也是单一访问的,如果说什么的话,静态状态比单例更加"单一访问",因为大多数单例实现都是有缺陷的,即你可以克隆单例,而静态则被定义为唯一的。 - PoweredByRice
2
什么是状态维护?什么是状态? - Kyle Delaney
5
@KyleDelaney:简单来说,“状态”是一个物体不同属性的组合,这些属性通常会随时间而变化。您可以搜索正式定义。 - Xaqron
显示剩余3条评论

93

静态类:

  1. 你不能创建静态类的实例。

  2. 当包含该类的程序或命名空间被加载时,静态类会被.NET Framework公共语言运行时(CLR)自动加载。

  3. 我们不能将静态类传递给方法。

  4. 在C#中,我们不能将一个静态类继承到另一个静态类中。

  5. 一种具有所有静态方法的类。

  6. 更好的性能(静态方法在编译时绑定)。

单例模式:

  1. 你可以创建对象的一个实例并重复使用它。

  2. 第一次请求时创建单例实例。

  3. 你可以创建单例类的对象并将其传递给方法。

  4. 单例类不限制继承。

  5. 我们可以释放单例类的对象,但无法释放静态类的对象。

  6. 方法可以被覆盖。

  7. 需要时可以延迟加载(静态类始终已加载)。

  8. 我们可以实现接口(静态类无法实现接口)。


21
静态类确实有构造函数:https://msdn.microsoft.com/zh-cn/library/k9x6w0hc.aspx - Tomer Arazy
3
是的,静态成员可以拥有一个只在该类内部可用的构造函数。当该类中的任何静态方法被调用时,这个构造函数会被调用。 - rahulmr
对于编译时的单例模式,它存储在堆内存中,但如果它被实例化一次,它会存储在栈中吗? - Luminous_Dev
@Luminous_Dev 不是的。任何单例实例归根结底都是一个对象实例,毫无疑问会存储在堆上。 - RBT
1
@rahulmr 重要的区别是:构造函数在创建第一个(也就是唯一的)实例之前也会被调用。 - CoolOppo
显示剩余2条评论

54

静态类是仅具有静态方法的类别,更好的词语是“函数”。静态类所体现的设计风格纯粹是过程化的。

而单例模式则是面向对象设计中特定的一种模式。它是一个对象实例(具有多态等内在可能性),创建过程确保该特定角色在其整个生命周期内只有一个实例。


1
单例模式根本不涉及多态。 - Iraimbilanja
33
你这样认为。我持有不同看法;例如,想象一个返回接口的单例工厂。你知道你得到的是 ISingleton(并且它永远是同一个),但不一定知道具体的实现是哪个。 - Morendil
1
嵌套的静态类也可以拥有实例方法,不仅限于只有静态方法。编写代码并查看即可。 - nanosoft
在具有更好对象模型的语言中(例如Ruby),类也是对象。静态类的“纯过程式”方面是语言强加的任意限制。 - Max

37
在单例模式中,您可以将单例创建为派生类型的实例,但是无法使用静态类实现此功能。
快速示例:
if( useD3D )
    IRenderer::instance = new D3DRenderer
else
    IRenderer::instance = new OpenGLRenderer

42
这并不是真正的单例模式,对我来说更像是工厂模式。 - vava
10
两者之间的根本区别在于Singleton会“缓存”其单个对象并不断返回(对其的引用),而工厂模式则会创建新的实例。 - Mystic
13
那么它就是代理单例模式 :) - vava
3
我知道这个 Singleton 的变种被称为 MonoState。 - Huppie
示例是工厂模式。 - R D

29

针对Jon Skeet的回答进行进一步阐述。

单例模式和一堆静态方法之间的最大区别是,单例模式可以实现接口(或者派生自有用的基类,虽然这种情况不太常见),因此您可以将单例模式传递给其他函数,就好像它是“另一个”实现一样。

在进行单元测试时,使用单例模式更容易。无论您在哪里将单例模式作为参数(构造函数、setter或方法)传递,都可以替换一个模拟或存根版本的单例模式。


我认为你不能直接模拟一个单例。难道你不需要声明一个接口,让单例和模拟类都实现它吗? - Ellen Spertus
@espertus 为什么不能对单例进行模拟?使用Mockito的示例:MySingleton mockOfMySingleton = mock(MySingleton.class) - Mike Rylander
你说得对,你可以使用像Mockito这样使用反射的工具来模拟它。我的意思是你不能直接通过子类化并重写其方法来模拟它。 - Ellen Spertus
@espertus 为什么不呢?当你实例化要测试的对象时,你可以在任何原本使用原始对象的地方替换为子类实现的单例。例如:new ClazzToTest(mockSingleton); - Mike Rylander
好的。我想我应该这样说:如果你通过将构造函数设为私有来实现单例模式,那么你就不能在不使用反射或创建一个单例和模拟类都实现的接口的情况下进行模拟。 - Ellen Spertus
显示剩余2条评论

26

这是一篇好文章:http://javarevisited.blogspot.com.au/2013/03/difference-between-singleton-pattern-vs-static-class-java.html

静态类

public class Animal {
    public static void foo() {
        System.out.println("Animal");
    }
}

public class Cat extends Animal {
    public static void foo() {  // hides Animal.foo()
        System.out.println("Cat");
    }
}

单例模式

总之,我只会将静态类用于存放实用方法,并将单例模式用于其他所有内容。


编辑


4
我不了解Java,但在.Net中,你最后提到的两点是错误的。静态类可以引用静态属性和字段,因此在状态上它们是相等的。而且它们是延迟加载的--静态构造函数在以下情况下运行:1)创建该类的实例。2)引用该类的任何静态成员。其中1不适用,因此只剩下2。因此,静态类直到首次使用时才被加载。 - jmoreno
1
对于静态类,虽然你不能重写静态方法,但是你可以隐藏它的父类中的静态方法。 - Max Peng
如果 Animal animal = new Cat();,那么 animal.foo(); 会发生什么? - Luminous_Dev
@jmoreno 静态类在第一次使用之前不会被加载吗?我相信它在编译时存储在堆栈内存中,并且可以立即访问..不是吗? - Luminous_Dev
@Luminous_Dev:至少对于.NET来说,静态类具有在第一次访问时运行的构造函数,因此它不是立即可访问的。理论上,静态构造函数可能需要无限长的时间。它(或任何其他类)存储的位置是实现细节,与这个问题并不相关。 - jmoreno
如果类具有状态(状态管理=一堆字段),则首选“单例模式”。 - Ahmed Nabil

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