单例模式是GoF模式书中一个完全付费的成员,但最近它似乎被开发人员世界孤立了。我仍然使用了相当多的单线程,特别是对于工厂类,虽然你必须对多线程问题(像任何类一样)小心一点,但我不明白为什么它们如此糟糕。
Stack Overflow似乎特别假设每个人都认为Singleton是邪恶的。为什么?
请用“事实、参考资料或具体专业知识”来支持您的回答
单例模式是GoF模式书中一个完全付费的成员,但最近它似乎被开发人员世界孤立了。我仍然使用了相当多的单线程,特别是对于工厂类,虽然你必须对多线程问题(像任何类一样)小心一点,但我不明白为什么它们如此糟糕。
Stack Overflow似乎特别假设每个人都认为Singleton是邪恶的。为什么?
请用“事实、参考资料或具体专业知识”来支持您的回答
当前回答
单例模式本身不是问题。问题是,这种模式经常被使用面向对象工具开发软件的人所使用,而没有对OO概念的扎实掌握。当在这个上下文中引入单例时,它们往往会成长为不可管理的类,这些类包含了每个小用途的帮助器方法。
从测试的角度来看,单身也是一个问题。它们往往使孤立的单元测试难以编写。控制反转(IoC)和依赖注入是旨在以面向对象的方式解决这个问题的模式,这有助于单元测试。
在垃圾收集环境中,单体很快就会成为内存管理的问题。
还有多线程场景,单线程可能成为瓶颈和同步问题。
其他回答
单身汉——反模式!马克·拉德福德(Mark Radford)的《过载杂志》(Overload Journal#57–2003年10月)很好地解释了为什么辛格尔顿被视为反模式。本文还讨论了替代Singleton的两种替代设计方法。
单线图的一个相当糟糕的地方是,你不能很容易地扩展它们。如果你想改变他们的行为,你基本上必须构建某种装饰模式或类似的东西。此外,如果有一天你想用多种方式来做这一件事,那么改变可能会很痛苦,这取决于你如何布局代码。
需要注意的一点是,如果您确实使用了单体,请尝试将它们传递给需要它们的人,而不是让他们直接访问。。。否则,如果您选择使用多种方式来完成单例所做的事情,那么如果每个类都直接访问单例,那么很难进行更改,因为每个类都嵌入了一个依赖项。
所以基本上:
public MyConstructor(Singleton singleton) {
this.singleton = singleton;
}
而不是:
public MyConstructor() {
this.singleton = Singleton.getInstance();
}
我认为这种模式被称为依赖注入,通常被认为是一件好事。
就像任何模式一样。。。思考它,并考虑它在给定情况下的使用是否不合适。。。规则通常是被打破的,模式不应该随意应用。
还有一件关于单身汉的事,还没有人说过。
在大多数情况下,“singletony”是某个类的实现细节,而不是其接口的特征。控制容器的反转可能会对类用户隐藏此特性;您只需要将类标记为singleton(例如,在Java中使用@singleton注释),就可以了;IoCC将完成剩下的工作。您不需要提供对单例实例的全局访问,因为访问已经由IoCC管理。因此,IoC Singleton没有任何问题。
与IoC Singleton相反的GoF Singleton应该通过getInstance()方法在接口中暴露“singletony”,因此他们会受到上面所说的一切影响。
首先,一个类及其合作者应该首先实现其预期目的,而不是专注于依赖对象。生命周期管理(当实例被创建并且超出范围时)不应该是类责任的一部分。对此,公认的最佳实践是创建或配置一个新组件,以使用依赖注入来管理依赖关系。
通常,软件变得更加复杂,所以有多个状态不同的Singleton类的独立实例是有意义的。在这种情况下,提交代码来简单地抓取单例是错误的。使用Singleton.getInstance()可能适用于小型简单系统,但当需要同一类的不同实例时,它无法工作/扩展。
任何类都不应该被认为是一个单独的类,而应该是它的用法或如何使用它来配置依赖项的应用程序。对于快速而令人讨厌的应用程序来说,这并不重要——只是简单的硬编码表示文件路径不重要,但对于更大的应用程序,需要使用DI以更合适的方式分解和管理这些依赖关系。
单例在测试中引起的问题是其硬编码的单一用例/环境的症状。测试套件和许多测试都是单独的,并且是独立的,与单例硬编码不兼容。
单身者在集群方面也很糟糕。因为这样,您的应用程序中就不再有“恰好一个单例”了。
考虑以下情况:作为开发人员,您必须创建一个访问数据库的web应用程序。为了确保并发数据库调用不会相互冲突,您可以创建一个线程保存SingletonDao:
public class SingletonDao {
// songleton's static variable and getInstance() method etc. omitted
public void writeXYZ(...){
synchronized(...){
// some database writing operations...
}
}
}
因此,您可以确定应用程序中只存在一个singleton,并且所有数据库都通过这一个也是唯一的SingletonDao。您的生产环境现在如下所示:
到目前为止一切都很好。
现在,考虑您想在集群中设置web应用程序的多个实例。现在,你突然有了这样的想法:
这听起来很奇怪,但现在您的应用程序中有很多单例。这正是单例不应该是的:它有很多对象。如果您想对数据库进行同步调用,如本例所示,这尤其糟糕。
当然,这是单例用法错误的一个例子。但是这个例子的信息是:你不能相信你的应用程序中只有一个单例实例,特别是在集群方面。