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

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

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


当前回答

这是我认为目前为止答案中缺少的:

如果每个进程地址空间需要一个此对象的实例(并且您尽可能确信此需求不会改变),那么应该将其设置为单例。

否则,它不是单例。

这是一个非常奇怪的要求,用户几乎不感兴趣。进程和地址空间隔离是一个实现细节。它们仅在用户希望使用kill或Task Manager停止应用程序时才会对用户产生影响。

除了构建一个缓存系统之外,没有太多的原因可以让您如此确信每个进程只应有一个实例。日志记录系统怎么样?对于每线程或更细粒度的消息可能更好,这样您可以更自动地跟踪消息的来源。应用程序的主窗口如何?这取决于;也许出于某种原因,您希望所有用户的文档都由同一进程管理,在这种情况下,该进程中会有多个“主窗口”。

其他回答

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

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

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

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

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

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

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

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

太多人将非线程安全的对象放在单例模式中。尽管DataContext不是线程安全的,纯粹是一个工作单元对象,但我已经看到了DataContext(LINQ to SQL)以单例模式完成的示例。

单线态的问题是范围增加,因此耦合的问题。不可否认,在某些情况下,您确实需要访问单个实例,并且可以通过其他方式实现。

我现在更喜欢围绕控制反转(IoC)容器进行设计,并允许容器控制生命周期。这为依赖于实例的类提供了好处,使它们不知道存在单个实例的事实。将来可以更改单例的生存期。我最近遇到的一个例子是从单线程到多线程的简单调整。

FWIW,如果你尝试单元测试它时它是一个PIA,那么当你尝试调试、修复或增强它时,它就会变成PIA。

在我的头顶上:

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