最近几天,我一直在阅读有关Singleton模式的信息。人们普遍认为,需要它的场景很少(如果不是很少的话),可能是因为它有自己的一系列问题,例如
我开始了解这些问题背后的想法,但对这些问题并不完全确定。
就像在垃圾回收问题的情况下一样,在单例实现中使用静态方法(这是模式固有的),这是否值得关注?因为这将意味着静态实例将持续到应用程序。这是否会使内存管理降级(这仅意味着分配给单例模式的内存将不会被释放)?
当然,在多线程设置中,让所有线程都争用单例实例将成为瓶颈。但是使用此模式如何导致同步问题(当然,我们可以使用互斥锁或类似的东西来同步访问)。
从(单元?)测试的角度来看,由于单例使用静态方法(很难模拟或存根),因此它们可能会引起问题。对此不确定。有人可以详细说明这个测试问题吗?
谢谢。
最佳答案
在垃圾回收环境中,内存管理可能是一个问题。
在典型的单例实现中,一旦创建了单例,就永远无法销毁它。当单例较小时,这种非破坏性特性有时是可以接受的。但是,如果单例很庞大,那么您不必要地使用了过多的内存。
在具有垃圾收集器(如Java,Python等)的语言中,这是一个更大的问题,因为垃圾收集器始终会认为单例是必要的。在C++中,您可以通过delete
欺骗指针来作弊。但是,这将打开它自己的蠕虫 jar ,因为它应该是单例的,但是通过删除它,可以创建第二个蠕虫。
在大多数情况下,这种内存过度使用不会降低内存性能,但是可以认为与内存泄漏相同。如果单例很大,则会浪费用户计算机或设备上的内存。 (如果分配一个巨大的单例,可能会遇到内存碎片,但这通常是无关紧要的)。
在多线程环境中,它可能导致瓶颈并引入同步问题。
如果每个线程都访问同一个对象,并且您正在使用互斥锁,则每个线程必须等待,直到另一个线程解锁了单例。而且,如果线程极大地依赖于单例,那么由于线程要花费大部分生命来等待,因此您会将性能降低到单线程环境。
但是,如果您的应用程序域允许,则可以为每个线程创建一个对象-这样,线程无需花费时间等待,而是可以工作。
从测试角度来看头痛。
值得注意的是,单例的构造函数只能测试一次。您必须创建一个全新的测试套件才能再次测试构造函数。如果您的构造函数不使用任何参数,这很好,但是一旦接受参数,您将无法再进行有效的单元测试。
此外,您不能有效地对单例进行存根,并且对模拟对象的使用变得难以使用(有很多解决方法,但是麻烦多于其值(value))。继续阅读以了解更多...
(这也会导致不良的设计!)
单例也是不良设计的标志。一些程序员希望使他们的数据库类成为单例。他们通常认为:“我们的应用程序永远不会使用两个数据库。”但是,有时候会使用两个数据库,或者进行单元测试时,您将要使用两个不同的SQLite数据库。如果您使用单例,则必须对应用程序进行一些重大更改。但是,如果从一开始就使用常规对象,则可以利用OOP来按时高效地完成任务。
单例的大多数情况是程序员懒惰的结果。他们不希望将一个对象(例如数据库对象)传递给一堆方法,因此他们创建了一个单例,每个方法都将其用作隐式参数。但是,由于上述原因,这种方法令人反感。
如果可以的话,尽量不要使用单例。尽管从一开始它们就似乎是一种好方法,但通常通常会导致设计不佳,并且难以维护代码。