单例模式是GoF模式书中一个完全付费的成员,但最近它似乎被开发人员世界孤立了。我仍然使用了相当多的单线程,特别是对于工厂类,虽然你必须对多线程问题(像任何类一样)小心一点,但我不明白为什么它们如此糟糕。
Stack Overflow似乎特别假设每个人都认为Singleton是邪恶的。为什么?
请用“事实、参考资料或具体专业知识”来支持您的回答
单例模式是GoF模式书中一个完全付费的成员,但最近它似乎被开发人员世界孤立了。我仍然使用了相当多的单线程,特别是对于工厂类,虽然你必须对多线程问题(像任何类一样)小心一点,但我不明白为什么它们如此糟糕。
Stack Overflow似乎特别假设每个人都认为Singleton是邪恶的。为什么?
请用“事实、参考资料或具体专业知识”来支持您的回答
当前回答
单身汉还不错。只有当你做了一些全球独一无二的东西而不是全球独一无二的时候,才是糟糕的。
然而,有“应用程序范围服务”(想想让组件交互的消息传递系统)-这是一个单例调用,一个“MessageQueue”-类,它有一个方法“SendMessage(…)”。
然后,您可以在各地执行以下操作:
MessageQueue.Current.SendMessage(新邮件到达消息(…));
当然,还要做到:
MessageQueue.Current.RegisterReceiver(this);
在实现IMessageReceiver的类中。
其他回答
单线态的问题是范围增加,因此耦合的问题。不可否认,在某些情况下,您确实需要访问单个实例,并且可以通过其他方式实现。
我现在更喜欢围绕控制反转(IoC)容器进行设计,并允许容器控制生命周期。这为依赖于实例的类提供了好处,使它们不知道存在单个实例的事实。将来可以更改单例的生存期。我最近遇到的一个例子是从单线程到多线程的简单调整。
FWIW,如果你尝试单元测试它时它是一个PIA,那么当你尝试调试、修复或增强它时,它就会变成PIA。
我对单身汉如何糟糕的回答总是,“他们很难做对”。语言的许多基本组件都是单体(类、函数、名称空间甚至运算符),计算的其他方面(本地主机、默认路由、虚拟文件系统等)也是如此,这并非偶然。虽然它们会时不时地带来麻烦和挫折,但也能让很多事情变得更好。
我看到的两个最大的错误是:将其视为一个全球性的问题&未能定义Singleton闭包。
每个人都把辛格尔顿说成是全球性的,因为他们基本上是全球性的。然而,在一个全球性的世界中,很多(不幸的是,并非所有)糟糕的地方并不是来自于全球化,而是来自于你如何使用它。辛格尔顿也是如此。事实上,“单个实例”并不意味着“全局可访问”。它更多的是一种自然的副产品,而且考虑到我们所知道的所有负面影响,我们不应该急于利用全球可访问性。一旦程序员看到Singleton,他们似乎总是通过它的实例方法直接访问它。相反,您应该像导航任何其他对象一样导航到它。大多数代码甚至不应该意识到它正在处理Singleton(松耦合,对吗?)。如果只有一小部分代码像访问全局对象一样访问该对象,那么很多危害都会消除。我建议通过限制对实例函数的访问来实施它。
辛格尔顿的背景也非常重要。Singleton的定义特征是“只有一个”,但事实是它在某种上下文/名称空间中是“唯一的”。它们通常是以下之一:每个线程、进程、IP地址或集群一个,但也可以是每个处理器、机器、语言名称空间/类加载器/任何东西、子网、Internet等。
另一个不太常见的错误是忽视单身汉的生活方式。仅仅因为只有一个,并不意味着Singleton是某种无所不能的“一直都是,永远都会是”,也不是普遍可取的(没有开始和结束的对象违反了代码中所有有用的假设,只应在最绝望的情况下使用。
如果你避免了这些错误,辛格尔顿仍然可以成为一名医院,但它已经做好了准备,可以看到许多最严重的问题得到了显著的缓解。想象一个Java Singleton,它被明确定义为每个类加载器一次(这意味着它需要线程安全策略),具有定义的创建和销毁方法,以及指定何时和如何调用它们的生命周期,其“实例”方法具有包保护,因此通常可以通过其他非全局对象访问。仍然是潜在的麻烦来源,但麻烦肯定要少得多。
可悲的是,他们没有教好如何做单身汉的例子。我们教坏的例子,让程序员暂时不用它们,然后告诉他们它们是坏的设计模式。
首先,一个类及其合作者应该首先实现其预期目的,而不是专注于依赖对象。生命周期管理(当实例被创建并且超出范围时)不应该是类责任的一部分。对此,公认的最佳实践是创建或配置一个新组件,以使用依赖注入来管理依赖关系。
通常,软件变得更加复杂,所以有多个状态不同的Singleton类的独立实例是有意义的。在这种情况下,提交代码来简单地抓取单例是错误的。使用Singleton.getInstance()可能适用于小型简单系统,但当需要同一类的不同实例时,它无法工作/扩展。
任何类都不应该被认为是一个单独的类,而应该是它的用法或如何使用它来配置依赖项的应用程序。对于快速而令人讨厌的应用程序来说,这并不重要——只是简单的硬编码表示文件路径不重要,但对于更大的应用程序,需要使用DI以更合适的方式分解和管理这些依赖关系。
单例在测试中引起的问题是其硬编码的单一用例/环境的症状。测试套件和许多测试都是单独的,并且是独立的,与单例硬编码不兼容。
垄断是魔鬼,具有非只读/可变状态的单态是“真正的”问题。。。
在阅读了jason的回答中提到的单身汉是病态的骗子之后,我发现了这个小花絮,它提供了单身汉经常被滥用的最佳例子。
全局不好,因为:a.导致命名空间冲突b.它以不正当的方式暴露了国家说到单身汉a.调用它们的显式OO方式可以防止冲突,因此a点不是问题没有国家的单身汉(像工厂一样)不是问题。具有状态的单体可以分为两类,即不可变或一次写入多个(配置/属性文件)。这些都不错。可变单光子,这是一种参考持有者,就是你所说的那些。
在上一篇声明中,他指的是博客中的“单身汉是骗子”的概念。
这如何适用于垄断?
要开始垄断游戏,首先:
我们首先制定规则,这样每个人都在同一页上比赛一开始,每个人都有平等的开始为了避免混淆,只提供了一组规则规则在整个比赛中不允许改变
现在,对于那些没有真正发挥垄断作用的人来说,这些标准充其量是理想的。垄断的失败是很难接受的,因为垄断是关于金钱的,如果你输了,你就必须费力地看着其他玩家完成比赛,而损失通常是迅速而致命的。因此,规则通常会在某个时刻被扭曲,以牺牲其他玩家的利益为代价,为某些玩家的利益服务。
所以你在和朋友鲍勃、乔和艾德玩垄断游戏。你正在迅速建立你的帝国,并以指数级的速度消耗市场份额。你的对手正在削弱,你开始闻到血腥味(比喻)。你的好友Bob把所有的钱都投入了尽可能多的低价值财产,但他的投资回报率并没有达到预期的水平。鲍勃,由于运气不好,落在了你的木板路上,从此退出了比赛。
现在,游戏从友好的掷骰子变成了严肃的生意。鲍勃已经成为失败的榜样,乔和埃德不想像“那个家伙”那样结束。因此,作为主角,你突然成为敌人。乔和埃德开始练习幕后交易,幕后资金注入,被低估的换房,以及任何削弱你作为一名球员的事情,直到他们中的一人上升到最高。
然后,不是他们中的一个获胜,而是整个过程重新开始。突然间,一套有限的规则变成了一个移动的目标,游戏退化为一种社交互动,这将构成自《幸存者》以来每一部高收视率真人秀的基础。为什么,因为规则正在改变,而且对于它们应该代表什么/为什么/什么没有共识,更重要的是,没有一个人做出决定。在这一点上,游戏中的每一个玩家都在制定自己的规则,随后就会出现混乱,直到其中两个玩家太累了,无法继续玩游戏,并慢慢放弃。
因此,如果一个游戏的规则书准确地代表了一个单身者,那么垄断规则书就是一个滥用的例子。
这如何适用于编程?
除了可变单例存在的所有明显的线程安全和同步问题之外。。。如果您有一组数据,能够由多个不同的源同时读取/操作,并且在应用程序执行的整个生命周期内都存在,那么现在可能是退一步问“我在这里使用的数据结构类型正确吗”的好时机。
就我个人而言,我见过程序员滥用单例,将其用作应用程序中某种扭曲的跨线程数据库存储。在直接处理代码之后,我可以证明这是一个缓慢的过程(因为需要所有线程锁来保证它的线程安全),也是一个噩梦(因为同步错误的不可预测/间歇性),而且几乎不可能在“生产”条件下进行测试。当然,本可以使用轮询/信令来开发一个系统,以克服一些性能问题,但这并不能解决测试问题,当一个“真正的”数据库已经能够以更健壮/可扩展的方式完成相同的功能时,又何必麻烦呢。
只有当您需要单例提供的内容时,单例才是一个选项。写一个对象的只读实例。同样的规则也应该级联到对象的财产/成员。
参见Wikipedia Singleton_pattern
一些人还认为它是一种反模式,他们认为它被过度使用,在实际上不需要类的唯一实例的情况下引入了不必要的限制。[1][2][3][4]
参考文献(仅文章中的相关参考文献)
^亚历克斯·米勒。我讨厌的模式#1:辛格尔顿,2007年7月^Scott Densmore。为什么单身汉是邪恶的,2004年5月^史蒂夫·耶格。单身汉被认为愚蠢,2004年9月^J.B.Rainsberger,IBM。2001年7月,明智地使用你的单身