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

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

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


当前回答

这并不是说单体本身是坏的,而是GoF设计模式是坏的。唯一真正有效的论点是,GoF设计模型不适合测试,尤其是在并行运行测试的情况下。

只要在代码中应用以下方法,使用类的单个实例就是有效的构造:

确保将用作单例的类实现了一个接口。这允许使用相同的接口实现存根或模拟确保Singleton是线程安全的。这是给的。单例应该是简单的,而不是过于复杂。在应用程序的运行时,如果需要将单例传递给给定对象,请使用构建该对象的类工厂,并让类工厂将单例实例传递给需要它的类。在测试期间,为了确保确定性行为,将单例类创建为单独的实例,作为实际类本身或实现其行为的存根/模拟,并将其原样传递给需要它的类。不要使用在测试期间创建需要单例的被测对象的类因子,因为它将传递其单个全局实例,这违背了目的。

我们已经在我们的解决方案中使用了Singleton,并取得了巨大的成功,这些成功是可测试的,确保了并行测试运行流中的确定性行为。

其他回答

在我的头顶上:

它们加强了紧密耦合。如果您的单例驻留在与其用户不同的程序集上,那么如果没有包含单例的程序集,using程序集就永远无法运行。它们允许循环依赖,例如,程序集A可以有一个依赖于程序集B的单例,而程序集B可以使用程序集A的单例。所有这些都不会破坏编译器。

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

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

关于单元测试:

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

我对单身汉如何糟糕的回答总是,“他们很难做对”。语言的许多基本组件都是单体(类、函数、名称空间甚至运算符),计算的其他方面(本地主机、默认路由、虚拟文件系统等)也是如此,这并非偶然。虽然它们会时不时地带来麻烦和挫折,但也能让很多事情变得更好。

我看到的两个最大的错误是:将其视为一个全球性的问题&未能定义Singleton闭包。

每个人都把辛格尔顿说成是全球性的,因为他们基本上是全球性的。然而,在一个全球性的世界中,很多(不幸的是,并非所有)糟糕的地方并不是来自于全球化,而是来自于你如何使用它。辛格尔顿也是如此。事实上,“单个实例”并不意味着“全局可访问”。它更多的是一种自然的副产品,而且考虑到我们所知道的所有负面影响,我们不应该急于利用全球可访问性。一旦程序员看到Singleton,他们似乎总是通过它的实例方法直接访问它。相反,您应该像导航任何其他对象一样导航到它。大多数代码甚至不应该意识到它正在处理Singleton(松耦合,对吗?)。如果只有一小部分代码像访问全局对象一样访问该对象,那么很多危害都会消除。我建议通过限制对实例函数的访问来实施它。

辛格尔顿的背景也非常重要。Singleton的定义特征是“只有一个”,但事实是它在某种上下文/名称空间中是“唯一的”。它们通常是以下之一:每个线程、进程、IP地址或集群一个,但也可以是每个处理器、机器、语言名称空间/类加载器/任何东西、子网、Internet等。

另一个不太常见的错误是忽视单身汉的生活方式。仅仅因为只有一个,并不意味着Singleton是某种无所不能的“一直都是,永远都会是”,也不是普遍可取的(没有开始和结束的对象违反了代码中所有有用的假设,只应在最绝望的情况下使用。

如果你避免了这些错误,辛格尔顿仍然可以成为一名医院,但它已经做好了准备,可以看到许多最严重的问题得到了显著的缓解。想象一个Java Singleton,它被明确定义为每个类加载器一次(这意味着它需要线程安全策略),具有定义的创建和销毁方法,以及指定何时和如何调用它们的生命周期,其“实例”方法具有包保护,因此通常可以通过其他非全局对象访问。仍然是潜在的麻烦来源,但麻烦肯定要少得多。

可悲的是,他们没有教好如何做单身汉的例子。我们教坏的例子,让程序员暂时不用它们,然后告诉他们它们是坏的设计模式。

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

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

所以基本上:

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

而不是:

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

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

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

假设该模式用于模型的某个方面,而该方面是真正的单一模式,则该模式没有本质上的错误。

我认为这种反弹是由于它的过度使用,而这反过来又是因为它是最容易理解和实施的模式。