单例模式是GoF模式书中一个完全付费的成员,但最近它似乎被开发人员世界孤立了。我仍然使用了相当多的单线程,特别是对于工厂类,虽然你必须对多线程问题(像任何类一样)小心一点,但我不明白为什么它们如此糟糕。

Stack Overflow似乎特别假设每个人都认为Singleton是邪恶的。为什么?

请用“事实、参考资料或具体专业知识”来支持您的回答


当前回答

我想谈谈公认答案中的4点,希望有人能解释我为什么错了。

为什么在代码中隐藏依赖项不好?已经有几十个隐藏的依赖项(C运行时调用、OS API调用、全局函数调用),单例依赖项很容易找到(搜索instance())。“使某个东西全局化以避免传递它是一种代码气味。”为什么不传递某个东西以避免使其成为单例代码气味?如果您通过调用堆栈中的10个函数传递一个对象,只是为了避免一个单例,那么这样做好吗?单一责任原则:我认为这有点模糊,取决于你对责任的定义。一个相关的问题是,为什么将这个特定的“责任”添加到一个班级中很重要?为什么将一个对象传递给一个类比将该对象作为类内的单例使用更紧密地耦合?为什么会改变国家的持续时间?单例对象可以手动创建或销毁,因此控件仍然存在,您可以使其生存期与非单例对象的生存期相同。

关于单元测试:

并非所有的类都需要是单位已测试并非所有需要成为单元的类测试需要更改单例的实现如果它们确实需要进行单元测试确实需要改变实施方式,很容易从使用singleton来实现通过依赖项传递给它的singleton注射

其他回答

单例模式本身不是问题。问题是,这种模式经常被使用面向对象工具开发软件的人所使用,而没有对OO概念的扎实掌握。当在这个上下文中引入单例时,它们往往会成长为不可管理的类,这些类包含了每个小用途的帮助器方法。

从测试的角度来看,单身也是一个问题。它们往往使孤立的单元测试难以编写。控制反转(IoC)和依赖注入是旨在以面向对象的方式解决这个问题的模式,这有助于单元测试。

在垃圾收集环境中,单体很快就会成为内存管理的问题。

还有多线程场景,单线程可能成为瓶颈和同步问题。

单身者在集群方面也很糟糕。因为这样,您的应用程序中就不再有“恰好一个单例”了。

考虑以下情况:作为开发人员,您必须创建一个访问数据库的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应用程序的多个实例。现在,你突然有了这样的想法:

这听起来很奇怪,但现在您的应用程序中有很多单例。这正是单例不应该是的:它有很多对象。如果您想对数据库进行同步调用,如本例所示,这尤其糟糕。

当然,这是单例用法错误的一个例子。但是这个例子的信息是:你不能相信你的应用程序中只有一个单例实例,特别是在集群方面。

如果你使用得当且最少,单身汉并不是邪恶的。还有很多其他好的设计模式可以在某一点上替代单例的需求(也可以提供最佳结果)。但是一些程序员不知道这些好模式,在所有情况下都使用单例模式,这使得单例模式对他们来说是有害的。

因为它们基本上是面向对象的全局变量,所以通常可以用这样的方式设计类,这样就不需要它们了。

单线图的一个相当糟糕的地方是,你不能很容易地扩展它们。如果你想改变他们的行为,你基本上必须构建某种装饰模式或类似的东西。此外,如果有一天你想用多种方式来做这一件事,那么改变可能会很痛苦,这取决于你如何布局代码。

需要注意的一点是,如果您确实使用了单体,请尝试将它们传递给需要它们的人,而不是让他们直接访问。。。否则,如果您选择使用多种方式来完成单例所做的事情,那么如果每个类都直接访问单例,那么很难进行更改,因为每个类都嵌入了一个依赖项。

所以基本上:

public MyConstructor(Singleton singleton) {
    this.singleton = singleton;
}

而不是:

public MyConstructor() {
    this.singleton = Singleton.getInstance();
}

我认为这种模式被称为依赖注入,通常被认为是一件好事。

就像任何模式一样。。。思考它,并考虑它在给定情况下的使用是否不合适。。。规则通常是被打破的,模式不应该随意应用。