singleton模式是gof模式书中一个完全付费的成员,但最近它似乎被开发人员世界孤立了。我仍然使用大量的单线程,尤其是工厂类,虽然您必须对多线程问题(实际上像任何类一样)稍微谨慎一些,但我不明白为什么它们如此糟糕。
堆栈溢出似乎特别假定每个人都同意单例是邪恶的。为什么?
请以"事实、参考资料或特定专业知识"支持您的答案。
- 您应该考虑添加"最佳实践"标签。
- 我不同意(所以不是所有人)
- 我应该把这个问题作为单身男女优缺点的"官方"来源吗?现在,很多回答只是一句话,但我认为我们可以列出一个非常全面的列表,列出为什么它们会是坏的。
- 我不得不说,最近我试图修改代码时,使用了单例设计,这让我很伤心。就像我在空闲时间做的那样,我几乎懒得重构这个。生产力的坏消息。
- 答案中有很多"缺点",但我也希望看到一些很好的例子,当模式是好的,与坏的对比…
- 我不同意单身是坏的。但我同意他们似乎被过度使用了!
- 我认为他们得到一个坏的代表的原因是因为他们被过度使用,并且实现他们的人有太多的静态方法。他们不使用接口,而是使用类。这是一个典型的单例实现,这是一个糟糕的实现!
- 几个月前我写了一篇关于这个主题的博客文章:jalf.dk/blog/2010/03/…——让我直截了当地说。我个人无法想象一个单一的情况下,单一的解决方案是正确的。这并不意味着这种情况不存在,但是…称它们为稀有动物是轻描淡写的。
- 另见,关于程序员:单例模式
- @如果我不同意。仅仅因为它是单例的,并不意味着你必须通过MySingleton.sharedInstance()访问它,无论它在哪里使用。将它作为一个参数传递,例如,参见我的帖子:assoc.tumblr.com/post/51302471844/The Missed Single to‌&8203;n
- @Adammith并不意味着你必须这样做,但它意味着你可以这样访问它。如果你不打算这样访问它,那么就没有什么理由让它成为一个单例。所以你的论点实际上是,"如果我们不把它当作一个单独的个体来对待,那么制造一个单独的个体是没有坏处的。是的,太好了。我的车不开也不会污染。不过,如果一开始不买车就容易多了。(完全披露:我实际上没有车)
- @Adammith我不在Tumblr上,所以我不能直接对你的帖子发表评论,但实际上,你是说一个单件适合用来表示通常只有一个单件的东西?真的?真的?我休息一下。不要用单件的。如果你真的使用它们,至少要想出一个它们是有益的情况。
- @Hippietrail:你链接的讨论是重复的。请看这里:programmers.stackexchange.com/questions/252/…
- 共和党成员埃里希·伽玛(ErichGamma)在一次关于该书新版本的采访中说:"我赞成放弃单身。它的使用几乎总是设计的味道。)"informit.com/articles/article.aspx?P=1404056
- 阅读罗伯特·C·马丁的博客文章《小单身汉》是值得的,它推翻了一些反对单身汉的论点,同时仍然指出了一些真正的问题,并以"大多数时候我们不想[使用它们]"结尾。
- 这整个话题最糟糕的部分是,讨厌单身的人很少给出具体的建议来代替使用什么。例如,通过这篇文章,可以链接到期刊文章和自发布的博客,继续讨论为什么不使用单例(它们都是很好的原因),但是它们在替换方面非常薄弱。不过,很多人都在挥手。我们这些试图教新程序员为什么不使用单例的人没有很多好的第三方反例来指向,只有人为的例子。这是令人厌烦的。
- 我认为对设计模式有一个总体的意见是一个坏主意。所有的设计模式都有其优缺点,这通常取决于用例。
- @Tistrga我听说你和我的故事可能是相关的:作为一个Android开发者,我在很长一段时间里都是超级单打的对手;人们只/认为/他们有一个单打,但Android操作系统不能保证当你回到前台时会有相同的processID,所以大量的ANR和你的单打上帝现在在哪里?但是,我们/被/被/被保证使用相同的appid/uid;该应用程序是唯一的tru singleton。进入Dagger2,在这里,在DI图中几乎所有带有作用域注释的东西在默认情况下都是单例的——这是一个真正的单例。现在我爱他们=]
- 我现在就要发泄:关了门的人真的是自我膨胀了。你必须明白,如果C++中最好的人(即Habor萨特和Bjarne Stroustrup)说单身者是坏人,那么你应该停下来想想这是否是"一种观点"。作为一个面对类似情况的程序员,你想知道为什么它是好的,为什么它是坏的,并形成你自己的观点。所以,不,这不是基于意见的。提出问题的人会形成自己的意见,但回答问题的人应该如实回答。完全没有理由关闭这个。
- 这里有一篇关于singleton反模式的好文章。它很好地描述了痛点。
- @如果链接显示404?
- 刚刚给这个问题写了一个很长的答案stackoverflow.com/questions/45967619/…
- @Tistrga,呃,只是不要用任何东西来代替单件?用new创建对象有什么问题?new Xxx与Xxx.getInstance()有类似的可测试性问题,但是在较少的地方自动使用它,这使得在需要统一可测试性的情况下重构是可行的。
- 伊米比斯,这正是我写投诉时脑海中的挥手方式…另外,你不必对我挖苦,因为我不是为单身汉的使用辩护的人。我认为你的断言"自动在较少的地方使用它"是错误的,但这不是我关心的论点。
- @Tistrga不使用singleton,而是使用依赖项注入,在ioc容器的singleton作用域中注册单实例依赖项。如果您已经在使用DI或设计新的体系结构,这是非常简单的。
从布莱恩·巴顿转述:
它们通常被用作全局实例,为什么这么糟糕?因为您将应用程序的依赖项隐藏在代码中,而不是通过接口公开它们。使某个东西全局化以避免传递它是一种代码味道。
他们违反了单一责任原则:因为他们控制自己的创造和生命周期。
它们本质上导致代码紧密耦合。这使得在许多情况下,在测试中伪造它们相当困难。
它们在应用程序的生命周期中携带状态。这是对测试的另一个冲击,因为您最终可能会遇到需要订购测试的情况,这对于单元测试来说是一个很大的禁忌。为什么?因为每个单元测试都应该独立于其他单元测试。
- 我不同意你的看法。因为评论只允许600个字符,所以我写了一篇博文对此进行评论,请参阅下面的链接。jorudolph.wordpress.com/2009/11/22/singleton-considerations
- 在第1点和第4点上,我认为单例是有用的,实际上几乎完美地缓存数据(尤其是从数据库)。性能的提高远远超过了建模单元测试所涉及的复杂性。
- @dai-bok:"缓存数据(尤其是来自数据库的数据)"使用代理模式来实现这一点…
- 好吧,只是研究一下,Web应用的问题是,如果代理超出范围(垃圾收集或其他),这个缓存内存可能会被释放?接下来我可能会问一个愚蠢的问题。
- 哇,回答得很好。我可能在这里使用了过于激烈的措辞,所以请记住,我回答的是一个消极的问题。我的答案是一个简短的"反模式"列表,它是由不良的单例使用引起的。完全公开;我也经常使用单件。有更多中立提出的问题,这样可以成为优秀的论坛,当单身汉可以被认为是一个好主意。例如,stackoverflow.com/questions/228164/…
- 对单身者的不好意见是OO的结果。静态/全局数据仍然有它的位置,只是没有被元胡说八道包裹。
- @Matt Fair Point确实,有些语言比其他语言更好地处理这个问题。当然,围绕单例修辞后期的重点是面向类和面向对象的语言。
- 它们对于在多线程环境中进行缓存来说并不是那么好。通过多个线程争夺有限的资源,您可以轻松击败缓存。[由单身汉维护]
- 在某种意义上,DI容器是单件的。单件的。那也不好吗?它们也很难测试。
- 我想你是在用一个斯特拉曼式的论点。如果对象不是sigleton,而是在使用它们的函数中创建的,则会遇到与描述的问题相同的问题。这两个问题都是通过依赖注入来解决的。请看我的博客:assoc.tumblr.com/post/51302471844/The Mizzouble Single to‌&8203;n
- 我不同意这四点。在某些时候,单身可能是个糟糕的选择。但这四点并不是真正的原因。我们可以用单例模式本身很容易地解决这些问题(4点)。
- @斯里拉姆,但这一点是什么是如此糟糕的单件,所以如果你说这4点没有打破模式,那么你实际上是同意的答案的问题-这是人们如何虐待他们!顺便说一下,首先,如果您需要全局变量,工具箱对我来说是一个很好的解决方案。
- @我在博客里说"通常"。这取决于你想如何为你的计算机系统建模。像xfs这样的文件系统将多个物理硬盘作为一个整体呈现。要做一个单身汉,没有铁律可循。没有什么能阻止你务实。如果你读我的博客,你会发现我的例子都是关于务实的。
- 很好的解释!我认为单实例的唯一有效原因是,如果您的框架/环境已经很糟糕,并且您无法避免全局状态和全局访问点问题。但即使这样,您也可以做一些比通常的单例实现更不邪恶的事情。
- 减1。这个答案将一些测试框架的局限性归咎于单例测试,并将其包装成更加武断的BS。
- 我确实使用单例,我想解决的问题是:我不能控制初始化顺序。创建类时,不存在任何环境。创建应用程序时,环境尚未初始化。当用户界面出现在屏幕上时,已经有点晚了。
- 加上一点,但第二点不正确。类中的构造函数控制对象的创建,它们被认为是不错的。
- 所以,简单地说,window对象是否javascript是singleton?
- 所以你的意思是,在一个测试用例的拆卸中,你必须撤销你刚刚做的事情(在单例测试中)?我认为测试用例是独立的。
- 关于第二点,有没有听说过信息专家模式(抓模的一部分)?如果一个对象有必要的信息来创建它自己,那么它没有任何问题。
- 听起来很教条。
- 我发现在调用堆栈中通过一个10级的全局实例是非常痛苦的,而这正是单例程序来进行救援的地方-很容易获得任何你想要的东西,至于测试,那么,我们使用raii来确保犯罪现场得到恢复。
单例解决一个(而且只有一个)问题。
资源争用。
如果你有一些资源
(1)只能有一个实例,并且
(2)您需要管理单个实例,
你需要一个单身汉。
例子不多。日志文件是大文件。您不想只放弃一个日志文件。您要正确刷新、同步和关闭它。这是必须管理的单个共享资源的示例。
你很少需要单身。他们不好的原因是,他们感觉自己像一个全球性的,他们是一个完全付费的GOF设计模式书的成员。
当你认为你需要一个全球性的,你可能会犯一个可怕的设计错误。
- 硬件也是一个例子,对吗?嵌入式系统有很多硬件,可能会使用单件或一个大的?<微笑>
- 完全同意。有很多坚定的"这种做法是不好的"谈论浮动,没有任何认识到实践可能有它的位置。通常,这种做法只是"不好",因为它经常被滥用。只要应用得当,单例模式本身就没有什么问题。
- 实际上,从原则上讲,单例是非常罕见的(而且打印机队列肯定不是单例,日志文件也不是单例——请参阅log4j)。通常情况下,硬件只是巧合,而不是原则。所有连接到PC的硬件充其量只是一个巧合的单例(想想多台显示器、鼠标、打印机、声卡)。即使是一个价值5亿美元的粒子探测器,由于巧合和预算限制,也只是一个单粒子探测器——这在软件中不适用,因此:没有单粒子探测器。在电信领域,电话号码和物理电话都不是单件电话(想想ISDN,呼叫中心)。
- 硬件可能只有各种资源的一个实例,但硬件不是软件。没有理由将开发板上的单个串行端口建模为单个端口。事实上,这样建模只会使移植到具有两个端口的新板更加困难!
- 单串行端口是单端口。只有一个。必须绝对禁止多个实例。当您移动到一个只有两个端口的板上时,您会遇到一个复杂的问题,但由于您的资源非常有限,所以它相当于"两个单例":两个端口。
- 所以你要写两个相同的类,复制大量的代码,这样你就可以有两个代表两个端口的单例?只使用两个全局serialport对象如何?不把硬件建模为类怎么样?为什么你会使用单件,这也意味着全球访问?您希望代码中的每个功能都能够访问串行端口吗?
- 两个串行端口违反了单例原则。现在有和硬件一样多的实例,这不是单例的。它只是一个绑定到硬件的类。
- 执行一个规则,一次只能存在一个实例,这与需要一个singlton不同。大多数DI框架支持单一的创建,它为您提供了可测试性的所有好处,并强制执行一个实例。
- @符文FS。当一次只能存在一个实例时,您只需要一个单例。
- @S.lott我不同意需要的部分。单例是该问题的解决方案,但并不是该问题的唯一解决方案,而且由于单例很容易导致可测试性和耦合问题,所以我认为即使是针对设计用于解决的问题,单例也是一个糟糕的解决方案。最多只能使用一个,但不需要
- @我认为你可能混淆了因果关系,这是必要的和充分的。单例设计不等于"只能存在一个实例"。singleton并不总是遵循"只能存在一个实例",因为——正如您所说的——可能有其他选择。但是,对于单例设计,"只能存在一个实例"是必需的,因为单例设计不可能存在其他用途。"只有一个实例可以存在"对于单例设计来说是不够的,因为有其他选择。
- @洛特:那么让我说得更具体一点,因为我似乎不清楚我最初的评论可能是:"如果你有一些资源,(1)只能有一个实例,(2)你需要管理那个实例,你需要一个单实例。"是一个falsum,因为你可以用一个单实例来解决这个问题,所以不需要一个s i。就像你说的那样
- @符文fs:有必要。但使用单例的理由还不够。这是必要的,但正如你指出的,还不够。需要==需要。必需品!=充分性。
- 没有必要,因为你可以在没有单打的情况下过得去。从来没有,但很多开发人员似乎认为他们需要一个单独的实例,而实际上他们不需要。这就是我强调的一点,他们既不需要也不需要Nesecarry
- @void.pointer-如果需求稍后更改,可以重构。如果singleton模式可以通过不必将darn作为参数传递给许多方法调用来减少代码大小,那么为什么不采用更简单的方法呢?(除非您确定需要额外的端口,或者稍后很难重构。)
- 呵呵,整个角度服务/工厂模型都是围绕单件产品建造的,工作得很漂亮。任何面向视图的对象(如控制器或视图)都是一个具有作用域的实例,其他对象都是一个单例,并用作具有状态或服务方法的模型。拥有单一数据模型允许所有视图以不同的方式表示相同的数据。
- 这个目的也可以通过对象池模式来解决,不是吗?
- 这个答案完全正确,直到它说…稀有的…稀有,WTF?!这个星球上大约有20亿台(我相信)iOS和安卓手机。所有的设备编程——它的全部——都是关于单例的。(确切地说,这个答案解释得很好。)我的意思是,在iOS中,这个应用程序是单例的(它还能是什么?)手机上的每项服务(如GPS、时间、屏幕等)都是单件服务。我想还是有一些大型机Fortran的工作,但"很少"?!
- @乔布洛,我认为单件的定义有点扭曲了。虽然所有这些东西通常只能由一个类实例来表示,但我认为这不足以成为一个单例实例。如果他们真的是单身,那么我们永远无法在手机上安装多个屏幕、摄像头等,除非他们添加了类似screen2或camera2之类的类,然后要求开发者明确地调用这些单身。但是我以前没有在电话上做过编程…他们就是这样做的吗?
- 我们可以把javascripts中的window和document对象看作单例对象吗?
- 请注意,传统上计算机对监视器有单点式支持,这就是为什么20年后多监视器支持仍然很奇怪和麻烦的原因——因为它在模仿单个大监视器,以满足单点式设计的需要!(我的意思是在一个座位上有多个监视器;多座位的工作方式不同,但仅仅是因为他们设法找到了一种方法,让每个座位都有自己的一套单件座椅)
一些编程的势利小人看不起他们只是一个光荣的全球。正如许多人讨厌goto语句一样,还有一些人讨厌使用global的想法。我看到过一些开发人员为了避免一个全局而付出了非常大的努力,因为他们认为使用一个全局开发人员会承认失败。奇怪但真实。
实际上,单例模式只是一种编程技术,它是概念工具箱的有用部分。有时你可能会发现它是理想的解决方案,所以使用它。但是仅仅为了使用一个设计模式而使用它,就像拒绝使用它一样愚蠢,因为它只是一个全球性的。
- 我在singleton中看到的失败是人们使用它们而不是全局的,因为它们在某种程度上"更好"。问题是(正如我看到的那样),singleton在这些情况下带来的东西是不相关的。(例如,在第一次使用时构造对于在非单例中实现来说是微不足道的,即使它实际上没有使用构造函数来实现它。)
- "我们"看不起他们的原因是"我们"经常看到他们用错了,而且用错的方式太频繁了。我们知道他们有自己的事业。
- @菲尔,你说过"有时你可能会发现它是理想的溶液,所以要使用它"。好吧,那么在哪种情况下我们会发现单例有用呢?
- @Pacerier,当以下所有条件都成立时:(1)您只需要某个条件中的一个;(2)您需要在大量方法调用中将该条件作为参数传递;(3)您愿意接受某一天必须重构的机会,以换取不传递darn t而立即减少代码的大小和复杂性。到处闲逛。
- 我不认为这能回答任何问题,它只是说"有时它可能适合,有时它可能不适合"。好吧,但是为什么,什么时候?为什么这个答案不只是一个对适度的争论?
- @安蒂诺姆完全同意。为什么只有一个对象可以让许多对象在内存中漂浮?例如,如果您不能为一些公共代码支持静态方法,那么这是一个很好的选择。
- 像goto一样,singleton(和globals)可能有自己的位置可以使用它们,但是像所有的坏习惯一样,它们不应该被使用。在编程中既没有一个好的规则来避免不正确的使用,也没有一组好的用例可以定义singleton是正确模式的位置。软件开发的历史已经确凿地证明,任何可能被误用的东西都将被最大程度地误用。
- 是的,Some coding snobs是一个"有效的"和"专业的"解释,为什么单件是好的。这个答案被如此高的投票通过,真是太可怕了。请仔细阅读已接受的答案并重新考虑您的索赔。
来自谷歌的Misko Hevery就这个话题发表了一些有趣的文章…
单例是病态的说谎者,有一个单元测试的例子,说明了单例如何使理解依赖链和启动或测试应用程序变得困难。这是一个相当极端的虐待例子,但他提出的观点仍然有效:
Singletons are nothing more than global state. Global state makes it so your objects can secretly get hold of things which are not declared in their APIs, and, as a result, Singletons make your APIs into pathological liars.
所有的单例都消失了,这就说明依赖注入使得向需要它们的构造函数获取实例变得容易,这就减轻了在第一篇文章中谴责的糟糕的全局单例背后的潜在需求。
- 米斯科关于这个问题的文章是目前最好的。
- 第一个链接实际上并没有解决单例的问题,而是假设类内部存在静态依赖关系。可以通过传入参数来修复给定的示例,但仍然使用单例。
- @DGM:没错——事实上,文章的"基本原理"部分和"单一因素是原因"部分之间存在着巨大的逻辑分歧。
- Misko的信用卡文章就是滥用这种模式的一个极端例子。
- 阅读第二篇文章singletons实际上是所描述的对象模型的一部分。但是,它们不是全局可访问的,而是工厂对象专用的。
- 如果有人在这里思考,他肯定会得出结论,那实际上是有一个单身汉在那里。
- @杰森,不知怎的链接不会加载。被困在永久加载中…
我认为这种混淆是由于人们不知道单例模式的实际应用。我压力太大了。单件不是一种包装全局的模式。单例模式应该只用于确保在运行时存在给定类的一个且只有一个实例。
人们认为独生子是邪恶的,因为他们把它用于全球。正是因为这种困惑,辛格尔顿才被人看不起。拜托,别把单身和环球混为一谈。如果用于预期目的,您将从单例模式中获得极大的好处。
- 在整个应用程序中,这一实例是什么?哦。全球。"singleton不是一个包装全局的模式"或者是幼稚的,或者是误导的,正如定义所示,该模式将一个类包装在一个全局实例上。
- 当然,当应用程序启动时,您可以自由地创建类的一个实例,并通过接口将该实例注入到任何使用它的对象中。实现不应该关心只能有一个。
- 有全局变量(引用没有任何访问控制的内存)和有单例(引用有访问控制的内存)有很大的区别。
- @大牛:最后真的没有。当然,您不能随意地用另一个实例替换该实例。(除了你做的那些吸引人的瞬间。实际上,我以前见过setInstance方法。)这几乎不重要——那些"需要"一个单体的weenie也不知道关于封装或可变全局状态有什么问题,所以他很有帮助(?)为每一个提供设置器。单一的。字段。(是的,这发生了。很多。几乎我在野外见过的每一个单身汉,都是设计上的变化无常,而且常常是如此的尴尬。)
- 它在设计上应该是可变的,如果您有不变的单例,它可能可以被const对象替换,或者如果它没有任何字段,而不仅仅是实用程序库。从操作系统的角度来看,singleton和全局变量可能是相同的,但至少从开发人员的角度来看,这是巨大的差异。你试过调试程序吗?谁用单例变量和全局变量来捕获错误?setters是完全不同的问题,我见过纯数据对象,它们只设置和获取方法,这和singleton有什么关系?坏的设计就是坏的设计。
- @戴纽斯:这是相关的,因为你经常看到他们在一起,因为他们都与程序思维密切相关。有些人认为数据是"要做的事",而不是"要做的事"。这是一个完全有效的POV,IMO,只要你承认并接受它。但是这些人反而把代码推到对象和类中,以避免OO狂热者的不赞成的注视。就在那时,一切开始走向地狱。
- 你从隐藏在类中的全局状态中几乎一无所获。事实上,你固有的损失之一就是可测试性。至少对于诚实善良的全球,或者甚至是拥有全球所有事物的设定者的独生子,你有一种方法可以将世界重置为一个已知的状态。一个"精心设计"的单身汉通常不会给你这个。
- 对于许多错误地在OO编程中使用继承的人来说,我们是否应该声明它是错误的,并说您总是应该使用组合?
- @大牛:在很大程度上,我们已经有了。""喜欢组合胜过继承"已经有一段时间了。然而,当继承显然是最好的解决方案时,您当然可以自由地使用它。单例、全局、线程、goto等也一样。它们在许多情况下可能有效,但坦率地说,"有效"还不够——如果你想违背传统的智慧,你最好能够证明你的方法比传统的解决方案更好。我还没有看到这样一个单子模式的例子。
- 我完全同意在很多情况下,单件是最好的方式,但我也看到了设计,在哪里避免单件作出了一些可怕的(维护/速度)决定。是否使用静态类成员-这是一个单例。它经常使用吗?不,有时有用吗?是的。因为人们使用的是怪物车去上班,你不会说这些车不好,你教育人们使用合适的车去上班,或者去其他类型的旅行,不是吗?
- 为了避免我们互相推诿,我所说的不仅仅是一个全球可用的实例。有很多这样的案例。我所说的(特别是当我说"capital-s singleton"时)是gof的singleton模式,它将单个全局实例嵌入类本身,通过getInstance或类似名称的方法公开它,并防止第二个实例的存在。坦率地说,在这一点上,您甚至可能没有一个实例。
单件的一个相当糟糕的地方是你不能很容易地扩展它们。如果你想改变他们的行为,基本上你必须构建某种装饰图案或者类似的东西。另外,如果有一天你想有多种方法来做一件事,那么根据你的代码布局,改变可能会很痛苦。
需要注意的一点是,如果你确实使用单件,试着把它们传递给任何需要它们的人,而不是让他们直接访问它……否则,如果您曾经选择使用多种方法来完成singleton所做的事情,那么很难进行更改,因为如果每个类直接访问singleton,则会嵌入依赖项。
所以基本上:
1 2 3
| public MyConstructor(Singleton singleton) {
this.singleton = singleton;
} |
而不是:
1 2 3
| public MyConstructor() {
this.singleton = Singleton.getInstance();
} |
我相信这种模式被称为依赖注入,通常被认为是一件好事。
就像任何模式…想一想,并考虑在给定的情况下使用它是否不当…规则通常是被打破的,模式不应该被随意地不加思考地应用。
- 呵呵,如果你到处都这样做,那么你也必须到处传递一个对歌手的引用,这样你就不再有一个单身汉了。(在我看来,这通常是件好事。)
- @胡说,你在说什么?单例实例只是类的一个实例,它被实例化一次。引用这样一个单例并不能复制实际的对象,它只是引用它——您仍然得到相同的对象(读:singleton)。
- @M.Mimpen:不,Capital-S Singleton(这里讨论的是)是一个类的实例,它(a)保证只有一个实例存在,(b)通过类自己的内置全局访问点访问。如果你已经声明不应该再叫getInstance(),那么(b)就不再是真的了。
- @曹,我不明白你的意思,或者你不明白我对谁说的话——那是对比亚克·弗伦德·汉森说的。bjarke指出,一个单例的多个引用结果中有几个单例。这当然不是真的,因为没有深刻的副本。
- @米蓬:我认为他的评论更多的是指语义效果。一旦您禁止调用getInstance(),您就有效地消除了singleton模式和普通引用之间的一个有用区别。就代码的其余部分而言,单一性不再是一种属性。只有getInstance()的调用者才需要知道甚至关心有多少实例。只有一个调用者,类可靠地执行单一性所需的工作和灵活性比让调用者简单地存储引用并重用它所需的成本更高。
- @我真的不明白你的担心。singleton类将确保其实现中只有一个自身的副本。其余的代码不应该关心有多少个副本。代码只是调用传递给它的singleton类,对吗?
- @Legendlength:这是关于无根据的依赖。使用singleton的代码要么获取实例本身(从而无正当理由将自己绑定到singleton类),要么传递实例(从而避免了singleton模式——如果没有全局访问点,singleton不如只创建一个模式)。坦率地说,类几乎没有理由强制其实例的单一性。
单例模式本身并不是问题。问题是,这种模式经常被使用面向对象工具开发软件的人使用,而没有对OO概念有一个扎实的理解。在这种上下文中引入单例时,它们往往会成长为不可管理的类,这些类中每使用一点辅助方法就包含一个辅助方法。
从测试的角度来看,单例也是一个问题。它们往往使独立的单元测试难以编写。控制反转(IOC)和依赖注入(DependencyInjection)是以面向对象的方式来解决这个问题的模式,这有助于单元测试。
在垃圾收集环境中,单例可以很快成为内存管理方面的问题。
还有多线程场景,其中单例可能成为瓶颈和同步问题。
- 我知道这是一条有很多年历史的线。嗨@kimoz u说:单件可以很快成为记忆管理的一个问题。想更详细地解释一下关于singleton&garbage collection的问题。
- @Kimoz,问题是"为什么单子模式本身不是问题?"您只是重复了这一点,但甚至没有提供单例模式的一个有效用例。
- @托马斯,因为根据定义,一个单例只存在于一个实例中。因此,将唯一的引用赋给空值通常很复杂。这是可以做到的,但这意味着你完全控制了这个点,在这个点之后,你的应用程序中就不再使用单例了。这是非常罕见的,而且通常与单身爱好者所寻找的完全相反:一种简单的方法,使一个实例总是可以访问。在一些像吉他或匕首这样的模架上,不可能去掉一个单件,它将永远留在记忆中。(尽管集装箱提供的单件比自制的要好得多)。
使用静态方法实现单例。进行单元测试的人避免使用静态方法,因为它们不能被模仿或存根化。这个站点上的大多数人都是单元测试的支持者。避免它们的最普遍的惯例是使用控制模式的反转。
- 这听起来更像是单元测试的一个问题,它可以测试对象(单元)、函数(单元)、整个库(单元),但在类中任何静态的东西(也包括单元)都会失败。
- 你不想把所有的外部参考资料都修改成MOC吗?如果是,那么MOC Singleton有什么问题,如果不是,您真的在做单元测试吗?
- @戴纽斯:模拟实例比模拟类麻烦多了。可以想象,您可以从应用程序的其余部分提取正在测试的类,并使用一个假的Singleton类进行测试。然而,这使测试过程非常复杂。首先,现在您需要能够随意卸载类(在大多数语言中不是真正的选项),或者为每个测试启动一个新的虚拟机(阅读:测试可能需要数千倍的时间)。不过,对于第二种情况,对Singleton的依赖是一个实现细节,现在在您的测试中到处泄漏。
- PowerMock可以模拟静态内容。
- 模拟对象是否意味着创建真实对象?如果mocking不能创建一个真正的对象,那么为什么类是singleton或方法是静态的呢?
单例在集群方面也很糟糕。因为这样,您的应用程序中就不再有"恰好一个单例"。
考虑以下情况:作为开发人员,您必须创建一个访问数据库的Web应用程序。为了确保并发数据库调用不会相互冲突,可以创建一个线程save SingletonDao:
1 2 3 4 5 6 7 8
| public class SingletonDao {
// songleton's static variable and getInstance() method etc. omitted
public void writeXYZ(...){
synchronized(...){
// some database writing operations...
}
}
} |
因此,您可以确定应用程序中只存在一个单例,并且所有数据库都通过这个单例,并且只通过SingletonDao。您的生产环境现在如下所示:
到目前为止一切都很好。
现在,考虑在集群中设置Web应用程序的多个实例。现在,你突然有了这样的东西:
这听起来很奇怪,但现在您的应用程序中有许多单例。这正是单身汉不应该做的:拥有许多对象。如果您(如本例所示)希望对数据库进行同步调用,则这一点尤其糟糕。
当然,这是一个单例的错误用法的例子。但是这个示例的信息是:您不能相信应用程序中只有一个单例实例,尤其是在集群方面。
- 如果您不知道如何实现singleton,则不应该这样做。如果你不知道你在做什么,你应该先找到它,然后做你需要的。
- 这很有趣,我有个问题。那么,如果单例(每一个在不同的机器/JVM上)连接到一个数据库,究竟有什么问题呢?单例作用域仅适用于特定的JVM,即使在集群中也是如此。与其哲学上说这种特殊情况很糟糕,因为我们的意图是跨应用程序的单个对象,我倒愿意看到由于这种安排而可能出现的任何技术问题。
它很容易被用作全局变量。
依赖于单例的类相对来说更难单独进行单元测试。
垄断是魔鬼,而具有非只读/可变状态的单子是"真正"的问题…好的。
在读过《单身汉是病态的说谎者》之后,正如杰森的回答所暗示的那样,我遇到了这个小消息,它提供了一个最好的例子,说明了单身汉经常被误用。好的。
Global is bad because:
Ok.
-
a. It causes namespace conflict
-
b. It exposes the state in a unwarranted fashion
When it comes to Singletons
Ok.
-
a. The explicit OO way of calling them, prevents the conflicts, so point a. is not an issue
-
b. Singletons without state are (like factories) are not a problem. Singletons with state can again fall in two categories, those which are immutable or write once and read many (config/property files). These are not bad. Mutable Singletons, which are kind of reference holders are the ones which you are speaking of.
在最后一句话中,他指的是博客中"单身汉都是骗子"的概念。好的。
这如何适用于垄断?好的。
要开始垄断游戏,首先:好的。
- 我们先制定规则,这样每个人都在同一个页面上
- 每个人在比赛开始时都有平等的开始
- 为了避免混淆,只提供了一组规则
- 规则不允许在整个比赛中改变
现在,对于任何没有真正玩过垄断的人来说,这些标准充其量是理想的。垄断的失败是很难被接受的,因为垄断是关于金钱的,如果你输了,你就必须苦心经营地看着其他玩家完成游戏,而损失通常是迅速的和压倒性的。因此,规则通常会在某一点上被扭曲,以牺牲其他玩家的利益为代价,为某些玩家服务。好的。
所以你在和朋友鲍勃、乔和艾德玩垄断游戏。你正在迅速建立自己的帝国,以指数级的速度消费市场份额。你的对手正在削弱,你开始闻到血的味道(形象地说)。你的朋友鲍勃把他所有的钱都投入到尽可能多的低价值房地产的僵局中,但他的投资回报并不像他预期的那样高。鲍勃,作为一个不幸的打击,降落在你的木板路上,被从游戏中删除。好的。
现在游戏从友好的掷骰子到严肃的生意。鲍勃是失败的榜样,乔和埃德不想最后变成"那个家伙"。所以,作为领头羊,你突然成为敌人。乔和艾德开始练习桌下交易,背后的资金注入,低估房屋交换和一般任何削弱你作为一个球员,直到其中一个上升到顶部。好的。
然后,不是他们中的一个获胜,而是整个过程开始。突然,一组有限的规则变成了一个移动的目标,游戏退化成了社会互动的类型,这将成为每个幸存者的高等级真人秀节目的基础。为什么,因为规则正在改变,而且对于它们应该如何/为什么/代表什么没有共识,更重要的是,没有一个人做出决定。在这一点上,游戏中的每一个玩家都在制定自己的规则,混乱随之发生,直到两个玩家疲惫不堪,无法继续玩游戏,慢慢地放弃游戏。好的。
因此,如果一个游戏的规则书准确地代表了一个单例,那么垄断规则书就是一个滥用的例子。好的。
这如何适用于编程?好的。
除了可变单例存在的所有明显的线程安全和同步问题之外…如果您有一组数据,能够同时被多个不同的源读取/操作,并且在应用程序执行的整个生命周期中都存在,那么现在可能是退后一步并询问"我是否在使用正确类型的数据结构"的好时机。好的。
就我个人而言,我见过一个程序员滥用单例,将它用作应用程序中某种扭曲的跨线程数据库存储。在直接处理代码之后,我可以证明这是一个缓慢的过程(因为所有线程锁都需要使其线程安全)和一个噩梦(因为同步错误的不可预测/间歇性质),并且几乎不可能在"生产"条件下进行测试。当然,可以使用轮询/信令来开发一个系统来克服一些性能问题,但这并不能解决测试中的问题,而且,如果"真实"数据库已经能够以更健壮/可扩展的方式完成相同的功能,为什么还要费心呢?好的。
如果您需要Singleton提供的功能,那么Singleton只是一个选项。对象的只写一个只读实例。同样的规则也应该级联到对象的属性/成员。好的。好啊。
- 如果singleton兑现了一些数据怎么办?
- @如果单例程序按预定和/或固定的时间表进行更新,它将减少写入次数,从而减少线程争用。但是,除非模拟一个模拟相同用法的非单例实例,否则您将无法准确地测试与单例交互的任何代码。TDD的人可能会有一个合适的,但它会工作。
- @伊万普赖斯:即使是模仿,你也会对代码产生一些问题,比如说Singleton.getInstance()。支持反射的语言可以通过设置存储一个真正实例的字段来解决这个问题。不过,在我看来,一旦你开始与另一个班级的私有国家周旋,考试就变得不那么可信了。
我对单身者如何糟糕的回答总是"他们很难做对"。语言的许多基本组件都是单例的(类、函数、名称空间甚至运算符),计算的其他方面(localhost、默认路由、虚拟文件系统等)的组件也是单例的,这并不是偶然的。虽然它们会不时地引起麻烦和沮丧,但它们也能使很多事情变得更好。
我看到的两个最大的错误是:将其视为一个全球性的,并未能定义单例闭包。
每个人都把单身汉当作全球性的,因为他们基本上是这样。然而,在一个全球,许多(可悲的是,不是全部)的坏处本质上不是来自于全球,而是你如何使用它。单身也一样。实际上,"单实例"并不意味着"全局可访问"。它更像是一种自然的副产品,考虑到我们所知道的来自它的所有坏处,我们不应该如此匆忙地开发全球可访问性。一旦程序员看到一个单例,他们似乎总是通过它的实例方法直接访问它。相反,您应该像导航其他对象一样导航到它。大多数代码甚至不应该意识到它正在处理单例(松耦合,对吗?)。如果只有一小部分代码像访问全局对象一样访问对象,那么会造成很多危害。我建议通过限制对实例函数的访问来实现它。
单例上下文也非常重要。单例的定义特征是"只有一个",但事实是它在某种上下文/名称空间中是"只有一个"。它们通常是:每个线程、进程、IP地址或集群一个,但也可以是每个处理器、机器、语言命名空间/类加载器/任何东西、子网、Internet等。
另一个不太常见的错误是忽视独生子女的生活方式。仅仅因为只有一个并不意味着一个独生子是一个无所不能的"一直是并且永远是",也不意味着它是普遍可取的(没有开始和结束的对象违反了代码中所有有用的假设,并且应该只在最绝望的情况下使用。
如果你避免这些错误,单身汉仍然可以是一个皮塔,有点准备看到很多最坏的问题都得到了显著的缓解。想象一个Java SuntLon,它被显式定义为每个类加载器一次(这意味着它需要一个线程安全策略),具有定义的创建和销毁方法,以及一个生命周期,它指示何时以及如何调用它们,并且其"实例"方法具有包保护,因此它一般通过其他非全局对象访问。仍然是潜在的麻烦来源,但肯定更少的麻烦。
遗憾的是,这并不是教你如何做单身汉的好例子。我们教坏的例子,让程序员先用一下,然后告诉他们他们是一个坏的设计模式。
参见维基百科的singleton_模式
It is also considered an anti-pattern by some people, who feel that it is overly used, introducing unnecessary limitations in situations where a sole instance of a class is not actually required.[1][2][3][4]
参考文献(仅本文中的相关参考文献)
Alex Miller。我讨厌的模式1:singleton,2007年7月
^斯科特·登斯莫尔。为什么单身汉是邪恶的,2004年5月
Steve Yegge。2004年9月,单身汉被认为是愚蠢的。
^ J.B.Rainsberger,IBM公司。2001年7月,明智地使用你的单件衣服
- 关于这个模式的描述并不能解释为什么它被认为是邪恶的…
- 几乎不公平:"有些人也认为它是反模式,他们觉得它被过度使用,在实际不需要类的唯一实例的情况下引入了不必要的限制。"看看参考文献……不管怎样,我有无线电调频。
单实例不是单实例!
与其他答案不同的是,我不想谈论单身汉的问题,而是想向你展示他们在正确使用时是多么强大和令人敬畏!
- 问题:在多线程环境中,单例可能是一个挑战解决方案:使用一个单线程引导进程来初始化单实例的所有依赖项。
- 问题:很难模仿单身汉。解决方案:使用方法工厂模式进行模拟MyModel myModel = Factory.inject(MyModel.class);
你可以把MyModel映射到继承它的TestMyModel类,当MyModel被注入时,你会得到TestMyModel指令。
- 问题:单子可以导致记忆韭菜,因为他们从来没有解决。解决办法:好吧,把它们处理掉!在应用程序中实现回调以正确地处理单例,您应该删除与它们链接的任何数据,最后:从工厂中删除它们。
正如我在标题中所说,单例并不是单例的。
- singleton提高了可读性:你可以查看你的类,看看它注入了什么singleton来找出它的依赖关系。
- singleton改进了维护:一旦你从一个类中删除了一个依赖项,你就不需要去编辑其他类的一个大链接,这些类只是转移了你的依赖项(这是我的臭味代码@jim burger)
- singleton提高了内存和性能:当应用程序中发生某些事情,并且需要一长串回调才能实现时,您将浪费内存和性能,通过使用singleton,您将减少中间人,并提高性能和内存使用率(通过避免不必要的局部变量分配)。
- 这并不能解决我对singleton的主要问题,它允许从项目中的数千个类中的任何一个类访问全局状态。
- 这就是目的…
这并不是说单身汉本身就不好,而是GOF的设计模式。唯一有效的论点是,GOF设计模式不适合用于测试,尤其是在并行运行测试的情况下。
只要在代码中应用以下方法,使用类的单个实例就是有效的构造:
确保将用作singleton的类实现接口。这允许使用相同的接口实现存根或模拟
确保singleton是线程安全的。这是给定的。
单身汉的性格应该简单,而不是过于复杂。
在应用程序的运行时,如果需要将单例传递给给定的对象,请使用一个类工厂来构建该对象,并让类工厂将单例实例传递给需要它的类。
在测试和确保确定性行为的过程中,将singleton类创建为单独的实例,作为实际类本身或实现其行为的存根/模拟,并按原样传递给需要它的类。不要使用在测试期间需要单例的创建被测试对象的类因子,因为它将通过它的单个全局实例,这会破坏目标。
我们在解决方案中使用了单例,取得了巨大的成功,这些成功是可测试的,可以确保并行测试运行流中的确定性行为。
我想在接受的答案中说明4点,希望有人能解释我为什么错了。
为什么在代码中隐藏依赖项很糟糕?已经有几十个隐藏的依赖项(C运行时调用、OS API调用、全局函数调用)和单例依赖项很容易找到(search for instance())。
"使某个东西全局化以避免传递它是一种代码味道。"为什么不传递某个东西以避免使它单件化为一种代码味道呢?
如果您通过调用堆栈中的10个函数传递一个对象,只是为了避免一个单独的函数,这是不是太好了?
单一责任原则:我认为这有点含糊,这取决于你对责任的定义。一个相关的问题是,为什么要将这种特定的"责任"添加到类中?
为什么将一个对象传递给一个类会使它比从类内将该对象作为一个单例使用更紧密地耦合?
为什么它会改变状态持续的时间?可以手动创建或销毁singleton,因此控件仍然存在,您可以使生存期与非singleton对象的生存期相同。
关于单元测试:
- 不是所有的课程都需要单元测试
- 不是所有需要成为单元的类测试需要更改单件的实现
- 如果它们确实需要单元测试,确实需要改变实施,很容易把一门课从用一个单件singleton通过依赖关系传递给它注射。
- 1。所有那些隐藏的依赖?这些也很糟糕。隐藏的依赖总是邪恶的。但是在CRT和OS的情况下,它们已经存在了,而且它们是完成某些事情的唯一方法。(尝试在不使用运行时或操作系统的情况下编写一个C程序。)这样做的能力远远超过了它们的负面影响。我们代码中的单件事并没有得到这样的奢侈;因为它们牢牢地在我们的控制和责任范围内,所以每一种用法(阅读:每一个附加的隐藏依赖项)都应该是合理的,作为完成工作的唯一合理方法。实际上,他们中很少有人是这样的。
- 2。责任"通常被定义为"变更原因"。一个单身汉最终既管理了自己的一生,又完成了真正的工作。如果更改了作业的完成方式或对象图的构建方式,则必须更改类。但是,如果您有其他代码构建对象图,而您的类只是完成它的实际工作,那么初始化代码可以根据需要设置对象图。所有东西都更加模块化和可测试性,因为您可以插入测试双精度,随意地删除所有东西,并且不再依赖隐藏的移动部件。
- 三。不必。这就是重点。紧耦合是件坏事。考虑:如果您通过构造函数或其他方式传入一个类型为A的对象,那么该对象可以在必须执行某种操作时使用它。但该对象也可以是B类型,它继承自A。只要B正确地扩展了A(读:跟随LSP),那么该对象仍然可以做所有它喜欢的事情,而不必考虑B正在做的额外的SPIFF。但是如果你说A.getInstance(),突然之间要么你只能有一个a,要么你必须做一些扭曲以允许getInstance工作。
- 4。capital-s"singletons"不能手动创建或销毁。关键是,只能有一个实例,它在语义上持续应用程序的整个生命周期(尽管它通常只在第一次请求时初始化)。如果你只有一个例子,那就没什么大不了的了……但是你不再有一个单身汉了。您仍然拥有那些隐藏的移动部件,除非您将这些单个实例传递给想要使用它们的对象的构造函数。
- 关于单元测试:?所有类都应该进行单元测试。否则,您不能指向特定的代码块并说"问题就在那里"。?如果所有需要单元测试的类与可变的全局状态相关联,那么它们都是不可测试的。如果您正确地测试了所有类,并且它们都因此通过DI获得了它们的实例……那么singleton就不再需要是singleton了。您已经消除了对全局访问点的任何需求,对象不关心有多少实例。
- 赵:1。很高兴您认识到"做事情的能力大大超过了它们的负面影响",例如日志框架。如果不鼓励开发人员进行日志记录,那么通过函数调用层的依赖注入来强制传递全局日志记录对象是很糟糕的。因此,单子。三。只有当您希望类的客户机通过多态性控制行为时,紧耦合点才是相关的。通常情况并非如此。
- 4。我不确定"不能手动销毁"是"只能是一个实例"的逻辑含义。如果你是这样定义单例的,那么我们不是在谈论同一件事。所有类都不应该进行单元测试。这是一个商业决策,有时以上市时间或开发成本为准。
- 1。命令任何东西通过一堆层是邪恶的。每个对象应该只接受它需要做的事情。这并不意味着将一个记录器传递给您所创建的东西;它意味着不创建那个东西。我可能几乎支持单例日志记录,因为可能所有的东西都可以使用它,它的操作实际上不会影响程序。但对于有意义的物体来说,它们很糟糕。例如,如果您有一个拥有帐户的客户类,那么客户的构造函数不应该使用创建帐户所需的内容;它应该使用一个帐户。
- 三。如果您不想要多态性,那么您甚至不需要实例。您也可以将它设置为一个静态类,因为无论如何,您都将自己绑定到一个对象和一个实现上——至少这样,API就不会对您撒谎了。
- 4。我说的是Gof-Savered,Capital-S的"单身模式"。包含非公共构造函数和公共getInstance()方法的方法。要求只有一个实例存在的实例。它管理着一个真正的实例,并禁止第二个实例的存在——并且通过创建另一个实例(并且希望通过破坏OTI)来防止其他实例凌驾于其单一性之上。如果你不是在谈论这个,你只是在谈论一个全局变量或服务定位器。
- 所有类都应该进行单元测试。为了时间的缘故,跳过任何测试都是半途而废。当然,有很多时候(半途而废,越快越好)>(彻底的,越慢的)…但是不要犯错误,这并不能降低测试所有东西的重要性。不管谁是负责人,都刚刚决定上市时间比可靠性更重要。
- 对于日志记录,我更喜欢在"main"类中有一个日志记录对象。如果任何其他类需要记录,它们应该抛出一个异常或者以某种方式将错误发送到主类。然后,主类完成所有日志记录。
文斯休斯顿有这些标准,我觉得这是合理的:
Singleton should be considered only if all three of the following criteria are satisfied:
-
Ownership of the single instance cannot be reasonably assigned
-
Lazy initialization is desirable
-
Global access is not otherwise provided for
If ownership of the single instance, when and how initialization occurs, and global access are not issues, Singleton is not sufficiently interesting.
我不想对善恶之争发表评论,但自从春天到来后,我就没有用过它们。使用依赖注入几乎消除了我对单例、服务定位器和工厂的需求。我发现这是一个更高效和清洁的环境,至少对于我所做的工作(基于Java的Web应用程序)来说。
- 默认情况下,春豆不是单件的吗?
- 是的,但我的意思是"编码"单件。我还没有"编写一个单例"(即,根据设计模式使用一个私有的构造函数yada yada yada),但是是的,在Spring中,我使用的是单个实例。
- 所以当别人这样做的时候,没关系(如果我以后会用它),但是如果别人这样做,我不会用它,那是邪恶的吗?
模式本身没有任何问题,假设它被用于模型的某个方面,而这个方面是真正的单一的。
我认为这种反冲是由于它的过度使用,而这又是因为它是最容易理解和实现的模式。
- 我认为,这种反弹更多地与人们实践正式的单元测试有关,他们意识到自己是一个噩梦。
- 不知道为什么是-1。这不是最具描述性的答案,但他也没有错。
从纯粹主义的观点来看,单身是不好的。
从实践的角度来看,单例开发是一个权衡开发时间与复杂性的过程。
如果你知道你的申请不会改变那么多,他们是相当不错的选择。只需知道,如果您的需求以一种意外的方式发生变化(在大多数情况下,这是非常正常的),您可能需要对事物进行重构。
单例测试有时也会使单元测试复杂化。
- 我的经验是,从单身开始,即使是在短期内,也会伤害到你,而不是从长期来看。如果应用程序已经存在一段时间,并且可能已经感染了其他单例程序,那么这种影响可能会小一些。从头开始?不惜一切代价避开他们!想要对象的单个实例吗?让工厂管理这个对象的实例化。
- Afaik Singleton是一种工厂方法。所以我不明白你的建议。
- "一个工厂"!="无法与同一类中的其他有用内容分离的工厂方法"
singleton是一种模式,可以像其他任何工具一样使用或滥用。
单例程序的坏部分通常是用户(或者我应该说单例程序不适合于它设计的用途)。最大的违规者是使用一个单例作为假全局变量。
- 谢谢洛基:)正是我的观点。整个女巫狩猎让我想起了哥特人的辩论。工具是为那些知道如何使用它们的人准备的;使用你不喜欢的工具对你来说可能很危险,所以要避免使用它,但不要告诉别人不要使用/不要学会正确使用它。我不完全是"专业单身汉",但我喜欢这样的事实,我有这样的工具,我可以感觉到在哪里使用它是合适的。时期。
当您使用单例(比如日志记录器或数据库连接)编写代码,然后发现需要多个日志或多个数据库时,就会遇到麻烦。
单子使它们很难从普通物体移动到普通物体上。
另外,编写一个非线程安全的单例也太容易了。
您应该将所有需要的实用程序对象从一个函数传递给另一个函数,而不是使用单例。如果您将它们全部包装成一个助手对象,这可以简化,如下所示:
1 2 3 4 5
| void some_class::some_function(parameters, service_provider& srv)
{
srv.get<error_logger>().log("Hi there!");
this->another_function(some_other_parameters, srv);
} |
- 为每一种方法传递论点都是非常棒的,它应该至少涉及到前十种方法如何污染您的API。
- 这是一个明显的缺点,应该由语言通过参数来处理,这些参数以某种方式传播到嵌套调用中,但是如果没有这种支持,就必须手动完成。考虑到即使是自然的单例,像一个表示系统时钟的对象,也有潜在的麻烦。例如,您将如何在测试中涵盖与时钟相关的代码(比如多费率电表)?
- 取决于您测试的是什么,如果您不测试单例,您为什么关心它的行为,如果您的两个远程对象依赖于彼此的行为,这不是单例问题。
- 您能提供哪种语言可以传播到嵌套调用中吗?
- Lisp动态变量类似。至于测试,让我澄清一下。假设有一个singleton对象返回当前日期/时间。您需要通过向某些函数提供模拟的日期时间值来测试依赖于日期时间的代码。这是不容易实现的,除非您有类似于日期时间提供程序的东西,可以用模拟版本替换特定调用。
- 如果你有一个模块依赖于另一个模块,而你不知道/不能改变它,那么你将很难不去理会它是否是单件的。人们经常使用继承的方式,在那里他们应该使用组合,但你不会说继承是不好的,OOP应该不惜一切代价避免,因为有那么多人在那里做设计错误,还是你呢?
- @但你不能把它传给所有的方法。您只需将它传递给需要它的方法和类。是否希望隐藏类需要访问其他对象的事实?我们应该让客户列表全球化,这样我们就不必一直把它传递给其他类了吗?
- @但是你有类成员,不把它们传递给每个类方法,因为你想知道,哪个方法需要它们,或者你想知道?单件不是全局变量。
- 类应该足够小,以便对类中成员的"全局"访问没有问题。但是对于一个有500个类的程序来说,当这些类中的任何一个直接访问一个单例时都是很困难的。我对非全球的单身汉没有任何问题。我只讨论全局访问它们的常见情况:myconfiguration.getUsername()。
单例的问题是范围的增加,因此是耦合的问题。不可否认,在某些情况下,您确实需要访问单个实例,并且可以通过其他方式完成。
我现在更喜欢围绕控制反转(IOC)容器进行设计,并允许容器控制生命周期。这使得依赖实例的类受益于不知道存在单个实例这一事实。单身汉的一生可以在未来改变。我最近遇到过一个这样的例子,它很容易从单线程调整到多线程。
fwiw,如果你尝试单元测试它时它是一个pia,那么当你尝试调试、修复或增强它时,它会变成pia。
克里斯·赖斯最近在《没有评论的编码》杂志上发表了一篇关于这个主题的文章。
注意:没有注释的编码不再有效。但是,链接到的文章已被另一个用户克隆。
http://geekswithblogs.net/angeleyes/archive/2013/09/08/singleton-i-love-you-but-you re-bring-me-down-re-uploaded.aspx
单身也不错。只有当你使某个全球独一无二的东西不是全球独一无二的时候,这才是不好的。
但是,有"应用程序范围服务"(考虑一个使组件交互的消息传递系统)-这需要一个singleton、一个"messagequeue"-类,它有一个方法"sendmessage(…)"。
然后,您可以在各地执行以下操作:
messagequeue.current.sendmessage(新的mailarriedMessage(…);
当然,也要做到:
messagequeue.current.registerreceiver(this);
在实现IMessageReceiver的类中。
- 如果我想创建一个范围较小的第二个消息队列,为什么不允许我重用您的代码来创建它呢?单身汉阻止了这一点。但是,如果您刚刚创建了一个常规的消息队列类,然后创建了一个全局实例作为"应用程序范围"实例,那么我可以创建另一个实例以供其他使用。但是,如果您将类设置为单例,我将不得不编写第二个消息队列类。
- 另外,为什么我们不应该只拥有一个全局的customerderlist,这样我们就可以从任何地方(如messagequeue)很好地调用它呢?我相信两者的答案都是一样的:它实际上是一个全局变量。
还有一件关于单件的事没人说。
在大多数情况下,"单一性"是一些类的实现细节,而不是其接口的特性。控件容器的反转可以隐藏类用户的特性;只需要将类标记为单体(例如,用Java中的EDCOX1×0注释),就可以了;IOCC将完成其余的操作。您不需要提供对singleton实例的全局访问,因为该访问已经由iocc管理。因此,国际奥委会单打没有什么问题。
与ioc singletons相反的gof singletons应该通过getInstance()方法在接口中公开"singletity",这样它们就会受到上面所说的一切的影响。
- 在我看来,"singletnity"是一个运行时环境细节,不应该被编写类代码的程序员考虑。相反,它是由类用户作出的考虑。只有用户知道实际需要多少实例。
太多人将不安全线程的对象放在单例模式中。我见过以单例模式完成的DataContext(Linq-to-SQL)的示例,尽管事实上,DataContext不是线程安全的,只是工作对象的一个单元。
- 许多人编写不安全的多线程代码,这是否意味着我们应该消除线程?
- @大牛:事实上,是的。IMO默认的并发模型应该是多进程的,有一种方法可以让两个进程(1)轻松地相互传递消息,和/或(2)根据需要共享内存。如果你想分享所有的东西,线程是有用的,但是你从来没有真正想要它。它可以通过共享整个地址空间来模拟,但这也被视为反模式。
- @戴纽斯:当然,这是假设,诚实对善的并发性是必要的。通常,您所希望的就是能够在等待操作系统做其他事情的同时做一件事情。(例如,在从套接字读取数据时更新UI。)在大多数情况下,一个像样的异步API可能会使线程变得不必要。
- 所以,如果您有大量的数据矩阵,需要处理,最好在单个线程中进行处理,因为对许多人来说,这可能是错误的。
- @大牛:在很多情况下,是的。(如果将同步添加到混合中,线程处理实际上会减慢速度。这就是为什么多线程不应该是大多数人首先想到的主要例子。)在其他情况下,最好有两个进程共享所需的东西。当然,您必须安排进程共享内存。但坦率地说,我认为这是一件好事——至少比默认共享所有模式要好得多。它要求您明确地说出(理想情况下,要知道)哪些部分是共享的,因此哪些部分需要是线程安全的。
如果你使用得当且最少的话,单身并不是坏事。还有许多其他优秀的设计模式可以在某种程度上取代单例的需求(同时也提供了最佳的结果)。但是一些程序员不知道这些好的模式,并且在所有的情况下都使用了singleton,这使得singleton对他们有害。
- 令人惊叹的!完全同意!但你可以,也许应该,详细说明更多。例如,扩展哪些设计模式最常被忽略,以及如何"正确和最少地"使用单例。说起来容易做起来难!P
- 基本上,另一种方法是通过方法参数传递对象,而不是通过全局状态访问它们。
首先,一个阶级及其合作者应该首先实现他们的目标,而不是专注于义务人。生命周期管理(当实例超出范围时创建SND)不应是CLADSES职责的一部分。对此,公认的最佳实践是使用依赖注入来创建或配置一个新组件来管理依赖项。
通常,软件会变得更复杂,让"singleton"类的多个独立实例具有不同的状态是有意义的。在这种情况下,提交代码来简单地获取单例是错误的。对于小型的简单系统来说,使用singleton.getInstance()可能是可以的,但在需要同一类的不同实例时,它不能工作/伸缩。
任何类都不应该被认为是单例类,而应该是它的用法或如何使用它来配置从属项的应用。对于快速和讨厌的情况,这并不重要——只是Luke硬编码说文件路径并不重要,但是对于更大的应用程序,这些依赖性需要分解出来,并使用DI以更合适的方式进行管理。
单例在测试中导致的问题是它们的硬编码单例/环境的症状。测试套件和许多测试都是各自独立的,它们分别是与硬编码单例不兼容的东西。
因为它们基本上是面向对象的全局变量,所以您通常可以以这样的方式设计类,这样您就不需要它们了。
- 如果您的类不局限于一次实例,那么您将需要类中由信号量管理的静态成员,这些信号量的结果几乎是相同的!你建议的替代方案是什么?
- 我有一个带有"主屏幕"的巨大应用程序。这个屏幕打开许多较小的模态/非模态窗口/ui窗体。imho我觉得主屏幕应该是一个单独的控件,例如,应用程序远角的某个小部件希望在主屏幕的状态栏中显示其状态,它所要做的就是mainscreen.getInstance().setStatus("some text");作为替代方案,您打算做什么?把主屏幕传遍整个应用程序??:D
- @Salffirancis:我的建议是,你不要再拥有那些关心他们不应该关心的东西的东西了,你可以偷偷地在应用程序里到处乱搞。:)您的示例可以更好地处理事件。当你正确地处理事件时,一个小部件甚至不需要关心是否有一个主屏幕;它只是广播"嘿,事情发生了",以及订阅了"事情发生了"事件的任何内容(不管它是主屏幕、小部件还是其他完全的东西!)决定它想要如何回应。无论如何,OOP就是这样做的。:)
- @Salvin考虑在调试主屏幕时,如果它正被许多组件"静默地"更新,那么很难解释主屏幕。你的例子是一个完美的理由为什么单身汉是坏的。
当几个人(或团队)到达相似或相同的解决方案时,就会出现一种模式。很多人仍然使用单一形式的原件或使用工厂模板(Alexandrescu的现代C++设计中的良好讨论)。并发性和管理对象生命周期的困难是主要的障碍,前者按照您的建议容易管理。
像所有的选择一样,单身汉也有相当的起伏。我认为它们可以适度地使用,特别是对于在应用程序寿命期内生存的对象。事实上,它们类似于(也可能是)地球人,这可能引发了纯粹主义者。
这就是我认为到目前为止答案中所缺少的:
如果每个进程地址空间需要这个对象的一个实例(并且您尽可能确信这个需求不会改变),那么您应该将它设置为单例。
否则,就不是单身了。
这是一个非常奇怪的要求,用户几乎从未感兴趣。进程和地址空间隔离是一个实现细节。它们只在希望停止使用kill或任务管理器的应用程序时对用户产生影响。
除了构建一个缓存系统之外,没有太多的理由可以让您如此确定每个进程应该只有某个实例。测井系统怎么样?对于每个线程或更细粒度的线程来说,这样做可能更好,这样您就可以更自动地跟踪消息的来源。应用程序的主窗口怎么样?这取决于;也许出于某种原因,您希望所有用户的文档都由同一进程管理,在这种情况下,该进程中会有多个"主窗口"。
单打&ndash;反模式!Mark Radford(超载期刊57&ndash;2003年10月)很好地解释了为什么单件被视为反模式。本文还讨论了两种替代单例的设计方法。
- 弗兰克,链接似乎不起作用?
- 太晚了,抱歉回信!这个链接对我来说很好(刚用Opera和火狐尝试过)。
作者的一些反对意见:
如果你将来要让这门课不再单身,你会陷入困境。一点也不-在这种情况下,我只有一个数据库连接单例,我想把它变成一个连接池。请记住,通过标准方法访问每个单例:
myclass.instance实例
这类似于工厂方法的签名。我所做的只是更新实例方法以从池返回下一个连接-不需要其他更改。如果我们不使用单件的话,那就更难了。
单身汉只是一个花哨的世界不能反驳,但是所有的静态字段和方法也是如此——从类而不是实例访问的任何东西本质上都是全局的,我看不出有那么多关于静态字段的使用的压力?
不是说单身是好的,只是在这里推回一些"传统智慧"。
- 不,你错过了重点。在第一点中,我仍然没有创建另一个实例的选项。我希望,希望,祈祷,下次我打电话给getInstance(),它会给我一个不同的例子,但我仍然没有办法说"我有一个例子"。现在我需要另一个实例来执行另一项任务"。在第二点上,很多事情只是一些花哨的全局性问题,是的,但是单例技术将其与许多不需要的行李(例如,单例限制、复杂且容易出错的同步问题)结合起来。
- @伊万:可变静态场是邪恶的,这是单身汉的许多原因。如果你还没有看到对他们的反击,你就没有足够的努力了;EDCOX1,3个词只是"全局"的C/J/ Java拼写。:)对于静态函数来说,它们不是一个没有静态变量的问题;全局性最大的问题是它所带来的"远距离操作"和隐藏的依赖关系,而一个没有调用间存储空间(没有实例,也没有可变静态变量)的函数最终只能依赖于什么(和它能弄坏什么)。
- "一点也不-在这种情况下,我只有一个数据库连接单例,我想把它变成一个连接池。"您仍然希望这个类是单例的。它从单连接更改为单连接池。如果要将数据库服务器拆分为两个独立的服务器,您将被拧成一团。现在,您的连接池需要知道要连接到哪个服务器。如果不首先使用单例,dic中的唯一更改将是创建第二个具有第二个服务器详细信息的db连接对象,并将其插入所有移动到此db的对象中。
它模糊了关注的分离。
假设您有一个单例,您可以从类内的任何地方调用这个实例。你的课不再像原来那样纯洁了。您的类现在将不再对其成员和显式接收的成员进行操作。这会造成混乱,因为类的用户不知道类需要什么样的足够信息。封装的全部思想是向用户隐藏方法的方式,但是如果在方法中使用了单例,则必须知道单例的状态才能正确使用该方法。这是反OOP。
- 这个答案不太确定。任何物体都可以这样说。真正的问题是,可以从任何地方访问单例。干净的API和足够的文档可以防止客户端错误地使用它。
在我的头顶上:
它们强制紧密耦合。如果您的singleton驻留在与其用户不同的程序集上,则使用程序集在没有包含singleton的程序集的情况下无法运行。
它们允许循环依赖,例如,程序集A可以具有依赖程序集B的单例,程序集B可以使用程序集A的单例。所有这些都不会破坏编译器。