单例模式的真实世界应用是什么?

16

这里有一个相关的问题:为什么我需要单例设计模式? - Aziz
可能是 设计模式:何时使用单例模式? 的重复问题。 - Martin Geisler
10个回答

17

简单来说,单例模式的作用是提供对某个类的对象实例的全局访问,并保证该类型的对象只能被创建一次。

  • 它提供了对某个对象实例的全局访问
  • 它确保最多只能创建该类型的一个实例。

当需要同时实现以上两点时,使用单例模式。

然而,这种情况并不常见。通常我们会避免使用全局变量。构建应用程序时假设“如果存在多个实例,则出现问题”是危险的,因为通常发现该假设不成立。可能想要在本地缓存多个实例,或者需要多个数据库、多个日志文件,或者线程性能要求让每个线程拥有自己的实例。

因此,你不需要强制执行“只能存在一个实例”的假设,如果只需要一个实例,只需创建一个实例即可。但是,将构造函数公开可见,以便在必要时可以创建更多的实例。

换句话说,单例模式提供的两个特性都具有负面影响。一般来说,我们不希望数据具有全局性,也不希望无端限制灵活性。

如果确实需要单例模式提供的某个特性,请仅实现该特性,而不包括另一个特性。如果需要某个对象在全局范围内可访问,请将其定义为全局变量,而不是使用单例模式。如果确实需要强制执行仅存在一个实例的条件(我想不出任何情况下你需要这么做),则可以实现该条件,但不具备全局可见性。

我所看到的单例模式唯一的真实应用场景是“架构师阅读了GoF设计模式书籍,并决定无处不用设计模式”或者“某些停留在80年代的程序员不熟练面向对象编程,并想以过程化方式存储数据,这意味着将数据存储为全局变量。而单例模式听起来像一种面向对象的方式,可以避免受到批评。”

关键点在于单例模式将两个非常不同且通常很少需要的职责混合在一起。通常情况下,您希望每个对象中 最多只有一个 职责。


3
单例模式听起来像是一种用面向对象编程的方式创建全局变量的方法,而不会被批评。 - Carlos Rodriguez

8
在我的项目中,我们创建了一个日志记录器组件,用于将应用程序的日志记录到不同的来源(例如文本文件、XML 和数据库),基于配置。因此,有一个日志管理器在整个应用程序中创建仅一个日志记录器类的实例,用于记录消息、错误等。

7

Jon Skeet, 全球知名的 IT 技术专家 THE_SKEET,撰写了一篇精彩的文章,详细阐述了实现单例模式的多种正确方式(线程安全、延迟加载、高性能)。

请点击此处阅读。 存档链接

应广大读者要求,我在此转载 Skeet 版本:

public sealed class Singleton
{
    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            return Nested.instance;
        }
    }

    class Nested
    {
        // Explicit static constructor to tell C# compiler
        // not to mark type as beforefieldinit
        static Nested()
        {
        }

        internal static readonly Singleton instance = new Singleton();
    }
}

在这里,实例化是由对嵌套类的静态成员的第一次引用触发的,而这仅在Instance中发生。这意味着实现完全是懒惰的,但具有先前方法的所有性能优势。请注意,虽然嵌套类可以访问封闭类的私有成员,但反之则不然,因此需要在此处将instance设为内部的。这并不会引起任何其他问题,因为该类本身是私有的。为了使实例化变得懒惰,代码会更加复杂。

2
你发布的链接很有信息量,但是如果你能扩展一下你的回答并在这里添加更多信息会更好。 - Zaheer Ahmed
1
@ZaheerAhmed:我已经将相关部分内联化了。现在已经节省了数百万次页面点击。 - Andrei Rînea

3
我曾经使用单例模式的一个很好的例子是在一个需要从应用程序的几个地方调用 Web 服务组件的应用程序中。我需要维护状态、初始化一些字段,并维护调用和回调的队列,所以我不能只做静态调用。但是我希望在整个应用程序中只重复使用一个实例。我将这个“服务”类实现为单例,这样我就可以在整个应用程序中以响应许多不同事件的方式调用它,但所有这些事件都在一个单一的位置处理。
根据我的经验,我使用单例模式的情况是:当一个对象在应用程序的生命周期内将被多次使用、需要初始化和/或具有较大的内存占用量时。基本上,当您可能拥有许多实例并且这样做代价高昂时,只有一个类的一个实例非常有用。

1
请注意,此代码不是线程安全的:
get   
{
   if (instance == null)   
      instance = new Singleton();   
    return instance;   
}   

一个线程可以进入函数,通过空值测试然后被挂起。第二个线程可能会开始并通过空值测试。从那时起,两个线程将在某个时候创建它们自己的单例对象副本,其中只有一个被使用。

在像 C# 这样的垃圾收集语言中,这可能无关紧要,但如果单例控制除了内存之外的资源,则确实很重要。您需要使用 双重检查锁定模式来防止它。


你能在这里提供一个在C#中实现双重检查锁定模式的例子吗?这将非常有帮助。 - sangam

1
假设您的问题在标题中:
我曾经使用过一个COM对象,每个服务器只能有一个实例。我们通过单例将其暴露给整个ASP.NET应用程序。

1

1
我知道这个视频,它非常好,我向每个人推荐它 ;) - ivan_ivanovich_ivanoff
哇,那个视频完全改变了我的想法。现在非常清晰明了!非常感谢你的分享!! - Justas G

1

我使用单例模式的一种方式是为应用程序实现一个“主控制器”对象。这有点像您在VBA中获得的Application对象。

该对象执行各种启动和关闭任务。它还提供了对应用程序范围内设置和服务的访问。其中包括命令行参数和日志服务等值。


0

如果您有一个需要大量初始化的资源,并且需要多次使用,例如在使用ORM时使用对象上下文或某种连接,则可以发现单例模式非常有用。

显然,您必须注意保持该资源的活动状态不会比每次重新创建它更耗费资源。


-1

这不是一个单例,因为单例使用只读关键字提供线程安全。

例如,

public sealed class Singleton
{
    // private static Singleton instance; (instead of this, it should be like this, see below)
    private static readonly Singleton instance = new Singleton();

    static Singleton(){}

    private Singleton() {}

    public static Singleton Instance
    {
        get
        {
            if (instance == null)
                instance = new Singleton();
            return instance;
        }
    }
}

通过使用它,将提供线程安全和适当的单例模式(这就是它与静态类不同的地方)。您的单例模式代码不是线程安全的。

有关单例模式的更多信息,请查看以下链接:


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