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

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

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


当前回答

Chris Reath最近在《无注释编码》杂志上发表的关于此主题的文章。

注意:无注释编码不再有效。但是,链接到的文章已被其他用户克隆。

Link

其他回答

垄断是魔鬼,具有非只读/可变状态的单态是“真正的”问题。。。

在阅读了jason的回答中提到的单身汉是病态的骗子之后,我发现了这个小花絮,它提供了单身汉经常被滥用的最佳例子。

全局不好,因为:a.导致命名空间冲突b.它以不正当的方式暴露了国家说到单身汉a.调用它们的显式OO方式可以防止冲突,因此a点不是问题没有国家的单身汉(像工厂一样)不是问题。具有状态的单体可以分为两类,即不可变或一次写入多个(配置/属性文件)。这些都不错。可变单光子,这是一种参考持有者,就是你所说的那些。

在上一篇声明中,他指的是博客中的“单身汉是骗子”的概念。

这如何适用于垄断?

要开始垄断游戏,首先:

我们首先制定规则,这样每个人都在同一页上比赛一开始,每个人都有平等的开始为了避免混淆,只提供了一组规则规则在整个比赛中不允许改变

现在,对于那些没有真正发挥垄断作用的人来说,这些标准充其量是理想的。垄断的失败是很难接受的,因为垄断是关于金钱的,如果你输了,你就必须费力地看着其他玩家完成比赛,而损失通常是迅速而致命的。因此,规则通常会在某个时刻被扭曲,以牺牲其他玩家的利益为代价,为某些玩家的利益服务。

所以你在和朋友鲍勃、乔和艾德玩垄断游戏。你正在迅速建立你的帝国,并以指数级的速度消耗市场份额。你的对手正在削弱,你开始闻到血腥味(比喻)。你的好友Bob把所有的钱都投入了尽可能多的低价值财产,但他的投资回报率并没有达到预期的水平。鲍勃,由于运气不好,落在了你的木板路上,从此退出了比赛。

现在,游戏从友好的掷骰子变成了严肃的生意。鲍勃已经成为失败的榜样,乔和埃德不想像“那个家伙”那样结束。因此,作为主角,你突然成为敌人。乔和埃德开始练习幕后交易,幕后资金注入,被低估的换房,以及任何削弱你作为一名球员的事情,直到他们中的一人上升到最高。

然后,不是他们中的一个获胜,而是整个过程重新开始。突然间,一套有限的规则变成了一个移动的目标,游戏退化为一种社交互动,这将构成自《幸存者》以来每一部高收视率真人秀的基础。为什么,因为规则正在改变,而且对于它们应该代表什么/为什么/什么没有共识,更重要的是,没有一个人做出决定。在这一点上,游戏中的每一个玩家都在制定自己的规则,随后就会出现混乱,直到其中两个玩家太累了,无法继续玩游戏,并慢慢放弃。

因此,如果一个游戏的规则书准确地代表了一个单身者,那么垄断规则书就是一个滥用的例子。

这如何适用于编程?

除了可变单例存在的所有明显的线程安全和同步问题之外。。。如果您有一组数据,能够由多个不同的源同时读取/操作,并且在应用程序执行的整个生命周期内都存在,那么现在可能是退一步问“我在这里使用的数据结构类型正确吗”的好时机。

就我个人而言,我见过程序员滥用单例,将其用作应用程序中某种扭曲的跨线程数据库存储。在直接处理代码之后,我可以证明这是一个缓慢的过程(因为需要所有线程锁来保证它的线程安全),也是一个噩梦(因为同步错误的不可预测/间歇性),而且几乎不可能在“生产”条件下进行测试。当然,本可以使用轮询/信令来开发一个系统,以克服一些性能问题,但这并不能解决测试问题,当一个“真正的”数据库已经能够以更健壮/可扩展的方式完成相同的功能时,又何必麻烦呢。

只有当您需要单例提供的内容时,单例才是一个选项。写一个对象的只读实例。同样的规则也应该级联到对象的财产/成员。

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

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

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

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

作者的一些对位词:

如果你将来需要让班级不再单身,你就会陷入困境一点也不——我当时使用的是一个数据库连接单例,我想将其转换为一个连接池。请记住,每个单例都是通过标准方法访问的:

我的类别实例

这类似于工厂方法的签名。我所做的只是更新实例方法以返回池中的下一个连接,无需进行其他更改。如果我们没有使用单例的话,那就更难了。

单身汉只是花式的全球明星这一点无可厚非,但所有静态字段和方法也是如此——从类而不是实例访问的任何东西本质上都是全局的,我看不出静态字段的使用有多大阻碍?

并不是说单身汉很好,只是反驳了这里的一些“传统智慧”。

还有一件关于单身汉的事,还没有人说过。

在大多数情况下,“singletony”是某个类的实现细节,而不是其接口的特征。控制容器的反转可能会对类用户隐藏此特性;您只需要将类标记为singleton(例如,在Java中使用@singleton注释),就可以了;IoCC将完成剩下的工作。您不需要提供对单例实例的全局访问,因为访问已经由IoCC管理。因此,IoC Singleton没有任何问题。

与IoC Singleton相反的GoF Singleton应该通过getInstance()方法在接口中暴露“singletony”,因此他们会受到上面所说的一切影响。

首先,一个类及其合作者应该首先实现其预期目的,而不是专注于依赖对象。生命周期管理(当实例被创建并且超出范围时)不应该是类责任的一部分。对此,公认的最佳实践是创建或配置一个新组件,以使用依赖注入来管理依赖关系。

通常,软件变得更加复杂,所以有多个状态不同的Singleton类的独立实例是有意义的。在这种情况下,提交代码来简单地抓取单例是错误的。使用Singleton.getInstance()可能适用于小型简单系统,但当需要同一类的不同实例时,它无法工作/扩展。

任何类都不应该被认为是一个单独的类,而应该是它的用法或如何使用它来配置依赖项的应用程序。对于快速而令人讨厌的应用程序来说,这并不重要——只是简单的硬编码表示文件路径不重要,但对于更大的应用程序,需要使用DI以更合适的方式分解和管理这些依赖关系。

单例在测试中引起的问题是其硬编码的单一用例/环境的症状。测试套件和许多测试都是单独的,并且是独立的,与单例硬编码不兼容。