ChatGPT解决这个技术问题 Extra ChatGPT

静态类和单例模式之间的区别?

静态类和单例模式之间存在什么真正的(即实际的)区别?

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

根据语言实现和您的使用模式,Singleton 可能效率较低,因为您每次要使用它时都会调用 getInstance() 方法(尽管在大多数情况下可能是 it doesn't matter )。
已经有很多答案了。它实际上是一个 singleton 对象,其中 static 方法只是函数,是一个非 OO 实体。
当您希望允许第三方提供类的实现时,会有所不同。在这种情况下,您通常还需要一个工厂模式。请参阅agiletribe.wordpress.com/2013/10/08/…
IMO 这个答案总结得很好stackoverflow.com/questions/14097656/…

G
Geoffrey

是什么让您说单例或静态方法不是线程安全的?通常两者都应该实现为线程安全的。

单例和一堆静态方法之间的最大区别在于单例可以实现接口(或从有用的基类派生,尽管根据我的经验这不太常见),因此您可以传递单例,就好像它“只是另一个“ 执行。


好吧,如果您喜欢它,它们本质上都不是线程安全的,您必须使它们都成为线程安全的,所以它们之间没有区别。
除了不可变类型,你能举一个本质上是线程安全的例子吗?
致 Skeet:人们说单例不是线程安全的,意味着单例一直在线程之间不必要地共享,而堆栈对象在您需要时共享,这意味着您不必进行不必要的同步。
@Geek:想象一下单例实现了一个接口 Foo,并且您有一个将 Foo 作为参数的方法。通过这种设置,调用者可以选择使用单例作为实现——或者他们可以使用不同的实现。该方法与单例解耦。将其与类只有静态方法的情况进行比较——每段想要调用这些方法的代码都与类紧密耦合,因为它需要指定哪个类包含静态方法。
@AmirBareket:尽管根据单例设计模式,它不是单例 - 如果类本身允许创建多个实例,那么无论工厂做什么,它都不是单例 IMO。
R
Rachel

真正的答案是 Jon Skeet,on another forum here

单例允许访问单个创建的实例——该实例(或者更确切地说,对该实例的引用)可以作为参数传递给其他方法,并被视为普通对象。静态类只允许静态方法。


但是,如果您可以通过调用静态 getInstance() 方法从几乎任何地方访问同一个实例,为什么还要将 Singleton 作为参数传递呢?
@HenriqueOrdine所以它可以适应现有代码并提供接口?
@HenriqueOrdine 他们说的是静态类,而不是具有静态方法的类。无法实例化静态类。然而,如果您传递包含静态方法的(非静态)类的实例,则不能在实例上调用静态方法。
什么是静态类?至少在 Java 中,没有这样的东西。
@Goran 我最初对您的措辞感到非常困惑。您说“您不能在实例上调用静态方法”。我读到“如果你有一个实例化对象的引用,你就不能调用它可能有的任何静态方法”。这当然是不正确的。再次阅读几次后,我认为您的意思是“从静态方法内部,您无法访问类中的非静态对象”,这是正确的。想要为遇到此答案并阅读您的评论的这些概念的新手澄清这一点。
A
Adowrath

单例对象存储在堆中,而静态对象存储在堆栈中。我们可以克隆(如果设计者没有禁止的话)单例对象,但我们不能克隆静态类对象。单例类遵循 OOP(面向对象原则),静态类不遵循。我们可以使用 Singleton 类实现接口,但类的静态方法(或例如 C# 静态类)不能。


第二个说法是错误的。我们不能克隆 Singleton 对象。单例实现必须拒绝这个。如果你真的可以克隆 Singleton,那就不是 Singleton。
对于 Java,这是不正确的答案:单例和静态都不使用堆栈。
#1 并不重要。 #2 描述了一个有缺陷的实现。 #3 完全没有道理。
静态对象如何存储在堆栈中?调用方法时会创建新的堆栈帧,它存储方法的局部变量,方法返回时会删除此堆栈帧,并且这些局部变量会丢失。当然堆栈很快,但它不适合存储静态对象。
我无法理解对此的赞成票数量。 1)为什么Singleton必须存储在堆栈中?在 C# 或 Java 等托管语言中,数据存储在托管堆中,本地方法变量/参数除外。 2)如果你可以克隆它,那么它不是一个正确实现的单例。 3) Singleton 被称为 OOP 反模式;即如果可能的话,你应该避免的事情。 4)这是唯一正确的事情。
n
neil.johnson

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


+1 好,务实的观点。单例模式通常被过度使用,但在少数情况下它是合适的。另请参阅:agiletribe.wordpress.com/2013/10/08/…
您对多态的优势是正确的。这是最重要的一点
嵌套的静态类可以实现接口。尝试编码它,将工作。我可以编译代码而没有任何错误。
S
StephenA

static 类不适用于任何需要状态的东西。将一堆函数放在一起很有用,即 Math(或项目中的 Utils)。所以类名只是给了我们一个线索,我们可以在哪里找到函数,仅此而已。

Singleton 是我最喜欢的模式,我用它来一次性管理一些事情。它比 static 类更灵活,并且可以维护它的状态。它可以实现接口,从其他类继承并允许继承。

我在 staticsingleton 之间选择的规则:

如果有一堆函数应该放在一起,那么 static 是选择。其他任何需要对某些资源进行单一访问的东西都可以作为 singleton 来实现。


为什么静态类不应该做任何需要保存状态的事情?
@Trisped:您既无法精确控制初始化也无法完成。
你在“单例是我最喜欢的模式”中迷失了我。单例是一个如此尖锐的角落,它应该被视为一种反模式和一种模式。类可以很好地具有静态状态,这也是单次访问,如果任何静态状态比单例更“单次访问”,因为大多数单例实现都被破坏了,即。您可以克隆单例,而静态被定义祝福是唯一的。
保持状态是什么意思?什么是状态?
@KyleDelaney:简单地说 State 是对象的不同属性的组合,这些属性通常会随着时间而变化。你可以谷歌正式定义。
S
Savan Gadhiya

静态类:-

您不能创建静态类的实例。加载包含该类的程序或命名空间时,由 .NET Framework 公共语言运行时 (CLR) 自动加载。我们不能将静态类传递给方法。我们不能将静态类继承到 C# 中的另一个静态类。具有所有静态方法的类。更好的性能(静态方法在编译时绑定)

单身人士:-

您可以创建对象的一个实例并重复使用它。 Singleton 实例在用户请求时首次创建。您可以创建单例类的对象并将其传递给方法。单例类没有说任何继承限制。我们可以处理单例类的对象,但不能处理静态类的对象。方法可以被覆盖。需要时可以延迟加载(总是加载静态类)。我们可以实现接口(静态类不能实现接口)。


静态类确实有构造函数:msdn.microsoft.com/en-us/library/k9x6w0hc.aspx
是的,静态可以具有该类内部的构造函数。当调用类中的任何静态方法时,都会调用它。
对于编译时的单例,它存储在 HEAP 内存中,但如果它被实例化一次,它会存储在堆栈中吗?
@Luminous_Dev 不。任何单例实例在一天结束时都是一个对象实例。毫无疑问,它将存储在堆上。
@rahulmr 重要区别:构造函数也在创建第一个(仅限AKA)实例之前被调用。
M
Morendil

静态类是只有静态方法的类,更好的词是“函数”。静态类中体现的设计风格是纯程序化的。

另一方面,Singleton 是一种特定于 OO 设计的模式。它是一个对象的实例(具有其中固有的所有可能性,例如多态性),具有一个创建过程,可确保该特定角色在其整个生命周期内只有一个实例。


多态性根本不适用于单例
所以你认为。我的想法不同。 ;) 例如,想象一个返回接口的单例工厂。你知道你得到了一个 ISingleton(它永远都是一样的),但不一定是哪个实现。
嵌套的静态类也可以有实例方法,它不限于只有静态方法。编码它,你可以看到。
在具有更好对象模型的语言(例如 Ruby)中,类也是对象。静态类的“纯过程”方面是语言强加的任意限制。
D
Don Neufeld

在单例模式中,您可以将单例创建为派生类型的实例,而使用静态类则无法做到这一点。

快速示例:

if( useD3D )
    IRenderer::instance = new D3DRenderer
else
    IRenderer::instance = new OpenGLRenderer

这不是真正的单例模式,对我来说更像是工厂。
并非如此,两者之间的根本区别在于 Singleton 将“缓存”其单个对象并继续返回(引用)同一个对象。工厂模式将创建新实例。
然后是代理单例:)
嗯,我知道单例的这个品种是 MonoState。
例子是工厂模式
C
Community

Jon Skeet's Answer 上展开

单例和一堆静态方法之间的最大区别在于,单例可以实现接口(或派生自有用的基类,尽管这在 IME 中不太常见),因此您可以传递单例,就好像它是“只是另一个”实现一样。

在对类进行单元测试时,单例更容易使用。无论您在何处将单例作为参数(构造函数、设置器或方法)传递,您都可以替换为单例的模拟或存根版本。


我认为您不能直接模拟单身人士。您不必声明单例和模拟类都实现的接口吗?
@espertus 为什么你不能嘲笑你的单身人士?使用 mockito MySingleton mockOfMySingleton = mock(MySingleton.class) 的示例。
你是对的,你可以使用像 mockito 这样的工具来模拟它,它使用反射。我的意思是你不能通过继承它并覆盖它的方法来直接模拟它。
@espertus 为什么不呢?当您实例化您正在测试的对象时,您可以在您使用原始对象的任何地方替换您的单例的子类实现。例如:new ClazzToTest(mockSingleton);
我没有使用过 Mockito,但是除了使用反射之外,如何子类化具有私有构造函数的类,单例就是这种情况?相关讨论:stackoverflow.com/questions/2302179/mocking-a-singleton-class stackoverflow.com/questions/15939023/…
J
JackDev

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

静态类

具有所有静态方法的类。

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

不能覆盖方法,但可以使用方法隐藏。 (Java 中隐藏的方法是什么?甚至 JavaDoc 的解释都令人困惑) public class Animal { public static void foo() { System.out.println("Animal"); } } public class Cat extends Animal { public static void foo() { // 隐藏 Animal.foo() System.out.println("Cat"); } }

辛格尔顿

只能实例化一次的对象。

方法可以被覆盖(为什么 Java 不允许覆盖静态方法?)

比静态方法更容易模拟

更善于维持状态

总之,我只会使用静态类来保存 util 方法,而将 Singleton 用于其他所有内容。

编辑

静态类也是延迟加载的。谢谢@jmoreno(静态类初始化什么时候发生?)

静态类的方法隐藏。谢谢@MaxPeng。


我不了解java,但在.Net 中,您的最后两点是不正确的。静态类可以引用静态属性和字段,因此它们是相等的。它们是延迟加载的——静态构造函数在以下情况下运行: 1)创建类的实例。 2) 类的任何静态成员都被引用。 1 不适用,剩下 2。因此,静态类在第一次使用之前不会加载。
对于静态类,虽然您不能覆盖静态方法,但您可以将静态方法对其父级隐藏。
如果 Animal animal = new Cat(); 那么 animal.foo(); 会发生什么?
@jmoreno 静态类直到第一次使用才加载?我相信它在编译时存储在堆栈内存中。它可以立即访问.. 不是吗?
@Luminous_Dev:至少对于.net,静态类有一个在第一次访问时运行的构造函数,所以不,它不能立即访问。理论上,静态构造函数可能会花费无限的时间。它(或任何其他类的存储位置)是一个实现细节,这与这个问题并不真正相关。
A
Alex

单例的另一个优点是它可以很容易地序列化,如果您需要将其状态保存到磁盘或将其远程发送到某个地方,这可能是必要的。


P
Petruza

我不是一个伟大的 OO 理论家,但据我所知,我认为与 Singleton 相比,静态类唯一缺乏的 OO 特性是多态性。但是如果你不需要它,你当然可以使用静态类进行继承(不确定接口实现)以及数据和函数封装。

Morendil 的评论,“体现在静态类中的设计风格纯粹是程序性的”我可能是错的,但我不同意。在静态方法中,您可以访问静态成员,这与访问其单个实例成员的单例方法完全相同。

编辑:我现在实际上在想,另一个区别是静态类在程序开始时被实例化*并存在于程序的整个生命周期中,而单例在某些时候被显式实例化并且也可以被销毁。

* 或者它可能在第一次使用时被实例化,这取决于语言,我认为。


是的,其他人似乎都忽略了这样一个事实,即具有静态方法的类也可以具有私有静态字段,它仍然可以用来维护状态(并通过公共静态设置器/获取器将其中一些公开给客户端代码)。
d
developer747

为了说明 Jon 的观点,如果 Logger 是静态类,则无法完成下面所示的操作。类 SomeClass 期望将 ILogger 实现的实例传递到其构造函数中。

单例类对于依赖注入的可能很重要。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {

            var someClass = new SomeClass(Logger.GetLogger());
        }


    }

    public class SomeClass 
    {
        public SomeClass(ILogger MyLogger)
        {

        }
    }

    public class Logger : ILogger
    {
        private static Logger _logger;
        private Logger() { }

        public static Logger GetLogger()
        {
            if (_logger==null)
            {
                _logger = new Logger();
            }

            return _logger;
        }

        public void Log()
        {

        }

    }


    public interface ILogger
    {
         void Log();
    }
}

m
mzaink

单例被实例化。只是只创建了一个实例,因此 Singleton 中的单个实例。

另一方面,静态类不能被实例化。


静态类可以在 java 中进行很多实例化。阅读 docs.oracle.com/javase/tutorial/java/javaOO/nested.html。另请参阅我的回答stackoverflow.com/a/37114702/1406510
a
agnieszka

好吧,单例只是一个被实例化的普通类,但只从客户端代码间接地实例化一次。静态类未实例化。据我所知静态方法(静态类必须有静态方法)比非静态更快。

编辑:FxCop 性能规则描述:“不访问实例数据或调用实例方法的方法可以标记为静态(在 VB 中共享)。这样做之后,编译器将向这些成员发出非虚拟调用站点,这将阻止在运行时检查确保当前对象指针不为空的每个调用。这可以为性能敏感代码带来可衡量的性能提升。在某些情况下,无法访问当前对象实例代表正确性问题。我实际上不知道这是否也适用于静态类中的静态方法。


F
Faran Shabbir

主要区别在于:

单例有一个实例/对象,而静态类是一堆静态方法

单例可以通过接口进行扩展,而静态类则不能。

Singleton可以被继承,它支持SOLID原则中的开/关原则,另一方面静态类不能被继承,我们需要自己进行更改。

单例对象可以传递给方法,而静态类因为它没有实例,所以不能作为参数传递


O
Oleg Poltoratskii

与静态类的区别

JDK有单例和静态的例子,一方面java.lang.Math是一个带有静态方法的final类,另一方面java.lang.Runtime是一个单例类。

单例的优点

如果您需要维护状态而不是单例模式比静态类更好的选择,因为在静态类中维护状态会导致错误,尤其是在并发环境中,这可能会导致竞争条件,而没有足够的同步并行修改多个线程。

如果单例类是重对象,则可以延迟加载,但静态类没有这样的优势,并且总是急切地加载。

使用单例,您可以使用继承和多态来扩展基类、实现接口并提供不同的实现。

由于 Java 中的静态方法不能被覆盖,它们会导致不灵活。另一方面,您可以通过扩展来覆盖单例类中定义的方法。

静态类的缺点

为单例编写单元测试比静态类更容易,因为您可以在需要单例时传递模拟对象。

静态类的优点

静态类提供比单例更好的性能,因为静态方法是在编译时绑定的。

单例模式有多种实现方式,各有优缺点。

急切加载单例

双重检查锁定单例

按需初始化持有者成语

基于枚举的单例

每一个的详细描述都太冗长了,所以我只是放了一篇好文章的链接 - All you want to know about Singleton


A
Amir Bareket

从测试的角度来看,单例是更好的方法。与静态类不同,单例可以实现接口,您可以使用模拟实例并注入它们。

在下面的示例中,我将说明这一点。假设您有一个 isGoodPrice() 方法,它使用方法 getPrice() 并且您将 getPrice() 实现为单例中的方法。

提供 getPrice 功能的单例:

public class SupportedVersionSingelton {

    private static ICalculator instance = null;

    private SupportedVersionSingelton(){

    }

    public static ICalculator getInstance(){
        if(instance == null){
            instance = new SupportedVersionSingelton();
        }

        return instance;
    }

    @Override
    public int getPrice() {
        // calculate price logic here
        return 0;
    }
}

使用 getPrice:

public class Advisor {

    public boolean isGoodDeal(){

        boolean isGoodDeal = false;
        ICalculator supportedVersion = SupportedVersionSingelton.getInstance();
        int price = supportedVersion.getPrice();

        // logic to determine if price is a good deal.
        if(price < 5){
            isGoodDeal = true;
        }

        return isGoodDeal;
    }
}


In case you would like to test the method isGoodPrice , with mocking the getPrice() method you could do it by:
Make your singleton implement an interface and inject it. 



  public interface ICalculator {
        int getPrice();
    }

最终单例实现:

public class SupportedVersionSingelton implements ICalculator {

    private static ICalculator instance = null;

    private SupportedVersionSingelton(){

    }

    public static ICalculator getInstance(){
        if(instance == null){
            instance = new SupportedVersionSingelton();
        }

        return instance;
    }

    @Override
    public int getPrice() {
        return 0;
    }

    // for testing purpose
    public static void setInstance(ICalculator mockObject){
        if(instance != null ){
instance = mockObject;
    }

测试类:

public class TestCalculation {

    class SupportedVersionDouble implements ICalculator{
        @Override
        public int getPrice() { 
            return 1;
        }   
    }
    @Before
    public void setUp() throws Exception {
        ICalculator supportedVersionDouble = new SupportedVersionDouble();
        SupportedVersionSingelton.setInstance(supportedVersionDouble);

    }

    @Test
    public void test() {
          Advisor advidor = new Advisor();
          boolean isGoodDeal = advidor.isGoodDeal();
          Assert.assertEquals(isGoodDeal, true);

    }

}

如果我们采用静态方法来实现 getPrice() 的替代方案,则很难模拟 getPrice()。您可以使用 power mock 来模拟静态,但并非所有产品都可以使用它。


现在这不是线程安全的,并且在您访问接口实现的方式方面通常很讨厌。当然,拥有一个接口对于可测试性来说是很好的——但是为什么还要为单例而烦恼呢?完全避免单身;让一个类实现它用于生产目的,一个实现用于测试目的,并根据您正在做的事情注入正确的实例。根本不需要将单例耦合到它的调用者。
感谢您的反馈。使其线程安全非常简单。此外,我使用单例进行缓存。
是的,尽管开销毫无意义。同样,不使用单例更简单。
A
Alessandro Ornano

我同意这个定义:

“单个”一词意味着整个应用程序生命周期中的单个对象,因此范围在应用程序级别。静态没有任何对象指针,因此范围在应用程序域级别。此外,两者都应该实现为线程安全的。

您可以找到有关以下方面的有趣其他差异:Singleton Pattern Versus Static Class


E
Eranga Dissanayaka

一个显着的区别是 Singleton 附带的不同实例化。

对于静态类,它由 CLR 创建,我们无法控制它。使用单例,对象在第一个尝试访问的实例上被实例化。


T
Tilak

延迟加载接口的支持,以便可以提供单独的实现返回派生类型的能力(作为延迟加载和接口实现的组合)


嵌套的静态类在java中可以很好地实现接口。你的第二点是错误的。
C
Chris

在许多情况下,这两者没有实际区别,特别是如果单例实例从不更改或更改非常缓慢,例如保持配置。

我想说最大的区别是单例仍然是普通的 Java Bean,而不是专门的仅静态 Java 类。正因为如此,单例在更多情况下被接受;它实际上是默认 Spring Framework 的实例化策略。消费者可能知道也可能不知道它是一个被传递的单例,它只是把它当作一个普通的 Java bean。如果需求发生变化,而单例需要变成原型,就像我们在 Spring 中经常看到的那样,它可以完全无缝地完成,而无需对消费者进行一行代码更改。

前面有人提到静态类应该是纯过程的,例如 java.lang.Math。在我看来,这样的类永远不应该被传递,它们不应该持有静态 final 作为属性以外的任何东西。对于其他一切,请使用单例,因为它更灵活且更易于维护。


R
RK_Muddala

我们有我们的数据库框架,可以连接到后端。为了避免跨多个用户的脏读,我们使用单例模式来确保我们在任何时间点都有可用的单个实例。

在 C# 中,静态类不能实现接口。当单个实例类需要为业务合同或 IoC 目的实现接口时,这就是我使用单例模式而不使用静态类的地方

Singleton 提供了一种在无状态场景中维护状态的方法

希望对你有帮助。。


P
Paul R

在我写的一篇文章中,我描述了我对为什么单例比静态类好得多的观点:

静态类实际上不是规范类——它是一个包含函数和变量的命名空间 使用静态类不是一个好习惯,因为它违反了面向对象的编程原则 静态类不能作为其他参数传递静态类不适合“懒惰”初始化 静态类的初始化和使用总是很难跟踪 实现线程管理很困难


我会为英语语法复习它,但除此之外,这是一个有趣的阅读:)
这是更好的答案,因为它处理的是实际用例特定的问题,而不是实现细节。
C
Community

Singleton 类在应用程序生命周期中提供一个对象(只有一个实例)如 java.lang.Runtime 而静态类只提供静态方法如 java.lang.Math

Java 中的静态方法不能被覆盖,但是 Singleton 类中定义的方法可以通过扩展来覆盖。

单例类能够继承和多态来扩展基类,实现接口并能够提供不同的实现。而静态不是。

例如:java.lang.Runtime,是 Java 中的 Singleton 类,调用 getRuntime() 方法返回与当前 Java 应用程序关联的运行时对象,但确保每个 JVM 仅一个实例。


V
Vivek Vermani

一个。序列化 - 静态成员属于该类,因此不能被序列化。

湾。尽管我们已经将构造函数设为私有,静态成员变量仍然会被携带到子类中。

C。我们不能进行延迟初始化,因为所有内容都只会在类加载时加载。


A
Arpit Khandelwal

从客户端的角度来看,静态行为对客户端是已知的,但单例行为可以对客户端隐藏。客户可能永远不会知道只有一个实例,他一次又一次地玩弄。


C
Community

我阅读了以下内容,并认为这也很有意义:

照顾业务 请记住,最重要的 OO 规则之一是对象对自己负责。这意味着关于类生命周期的问题应该在类中处理,而不是委托给静态等语言结构。

来自《面向对象的思维过程》第 4 版一书。


我不同意,因为这实际上只是为类增加了责任,这(假设它做了任何事情)意味着它现在违反了单一责任原则。
S
Sanjay Dwivedi

我们可以创建单例类的对象并将其传递给方法。单例类没有任何继承限制。我们不能处理静态类的对象,但可以单例类。


如果始终只有一个方法并且始终具有静态引用,那么将单例传递给方法有什么用?
P
Premraj

Java 中的静态类只有静态方法。它是功能的容器。它是基于过程编程设计创建的。

单例类是面向对象设计中的一种模式。 Singleton 类在 JVM 中只有一个对象实例。这种模式以这样一种方式实现,即在 JVM 中始终只存在该类的一个实例。