首页 文章

单身人士有什么不好的? [关闭]

提问于
浏览
1792

singleton pattern是_320152的patterns book的完全付清成员,但它最近似乎是开发者世界的孤儿 . 我仍然使用相当多的单例,特别是对于factory classes,虽然你必须对多线程问题(实际上是任何类)有点小心,但我不明白为什么它们如此可怕 .

Stack Overflow特别假设每个人都同意Singletons是邪恶的 . 为什么?

请用“事实,参考或具体专业知识”支持您的答案

30 回答

  • 16

    单身人士的问题是范围扩大的问题,因此coupling . 无可否认,在某些情况下,您确实需要访问单个实例,并且可以通过其他方式完成 .

    我现在更喜欢围绕inversion of control(IoC)容器进行设计,并允许容器控制生命周期 . 这使得依赖于实例的类的好处是不知道存在单个实例的事实 . 单身人士的生命周期可以在未来改变 . 一旦我最近遇到的这样的例子是从单线程到多线程的简单调整 .

    FWIW,如果它是PIA,当你尝试进行单元测试时,那么当你尝试调试,错误修复或增强它时,它会进入PIA .

  • 4

    最近关于这个主题的文章由Chris Reath在Coding Without Comments撰写 .

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

    http://geekswithblogs.net/AngelEyes/archive/2013/09/08/singleton-i-love-you-but-youre-bringing-me-down-re-uploaded.aspx

  • 34

    首先,一个阶级及其合作者应该首先实现他们的预期目的,而不是专注于deoendents . 生命周期管理(当实例在超出范围时被修改时)不应该成为工作人员责任的一部分 . 最常见的做法是使用依赖注入来创建或配置新组件以管理依赖关系 .

    软件通常变得更复杂,因此具有不同状态的“单例”类的多个独立实例是有意义的 . 在这种情况下,提交代码以简单地获取单例是错误的 . 对于小型简单系统,使用Singleton.getInstance()可能没问题,但是当一个人可能需要同一个类的不同实例时,它不能工作/扩展 .

    任何类都不应该被认为是单例,而应该是它的用法或它如何用于配置依赖 . 对于快速和讨厌这没关系 - 只是卢克硬编码说文件路径无关紧要,但对于更大的应用程序,这种依赖需要考虑因素并使用DI以更合适的方式进行管理 .

    单例导致测试的问题是其硬编码单一用例/环境的症状 . 测试套件和许多测试都是单独的,并且与单个硬编码不兼容 .

  • 8

    一些编码的势利者瞧不起他们只是一个美化的全球 . 就像许多人讨厌goto声明一样,还有其他人讨厌使用全局的想法 . 我见过几个开发人员为了避免全球化而竭尽全力,因为他们考虑使用一个来承认失败 . 奇怪但真实 .

    在实践中,Singleton模式只是一种编程技术,是您的概念工具包的有用部分 . 您可能会不时发现它是理想的解决方案,因此请使用它 . 但是使用它只是为了让你夸耀使用设计模式就像拒绝使用它一样愚蠢,因为它只是一个全球性的 .

  • 20

    单身模式本身不是问题 . 问题在于,人们经常使用这种模式来开发具有面向对象工具的软件,而没有扎实地掌握OO概念 . 当在这种情况下引入单例时,它们往往会成长为无法管理的类,每个小用途都包含辅助方法 .

    从测试的角度来看,单身人士也是一个问题 . 他们倾向于使孤立的单元测试难以编写 . Inversion of control (IoC)和 dependency injection 是旨在以面向对象的方式克服此问题的模式,这有助于单元测试 .

    garbage collected环境中,单例很快就会成为内存管理方面的问题 .

    还存在多线程场景,其中单例可能成为瓶颈以及同步问题 .

  • 11

    Spring出现以来我一直在使用它们 . 使用dependency injection几乎完全取消了我对单件,服务器和工厂的要求 . 我发现这是一个更高效,更干净的环境,至少对于我所从事的工作类型(基于Java的Web应用程序) .

  • 205

    来自谷歌的Misko Hevery有一些关于这个话题的有趣文章......

    Singletons are Pathological Liars有一个单元测试示例,说明了单例如何使得很难找出依赖链并启动或测试应用程序 . 这是滥用的一个相当极端的例子,但他提出的观点仍然有效:

    单身人士只不过是全球化的国家 . 全局状态使得您的对象可以秘密地掌握未在其API中声明的内容,因此,单身人士会将您的API变成病态的骗子 .

    Where have all the Singletons Gone指出依赖注入使得向需要它们的构造函数获取实例变得容易,这减轻了第一篇文章中谴责的糟糕的全局Singletons背后的潜在需求 .

  • 52

    从Brian Button转述:

    • 它们通常用作全局实例,为什么这么糟糕?因为您在代码中隐藏了应用程序的依赖项,而不是公开它们通过接口 . 制作一些全局的东西以避免传递它是code smell .

    • 他们违反了single responsibility principle:因为他们控制着自己的创造和生命周期 .

    • 它们固有地导致代码紧密coupled . 在许多情况下,这使得将它们伪装在测试中相当困难 .

    • 它们在应用程序的生命周期中携带状态 . 测试的另一个打击因为你可能最终会遇到需要订购测试的情况,这对于单元测试来说是一个很大的问题 . 为什么?因为每个单元测试应该独立于另一个 .

  • 315

    关于单身人士的一个相当不好的事情是你不能很容易地扩展它们 . 如果你想改变他们的行为,你基本上必须构建某种decorator pattern或某些东西 . 另外,如果有一天你想要有多种方法来做这件事,那么根据你的代码布局方式进行更改可能会非常痛苦 .

    有一点需要注意,如果你使用单身人士,试着把它们传递给任何需要它们的人,而不是让他们直接访问它......否则,如果你选择有多种方式来完成单身人士所做的事情,那么它将是因为每个类在直接访问单例时嵌入依赖项,所以很难改变 .

    所以基本上:

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

    而不是:

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

    我相信这种模式被称为dependency injection,通常被认为是一件好事 .

    像任何模式一样......考虑一下并考虑它在给定情况下的使用是否不合适......规则通常会被破坏,并且不应该毫无顾忌地应用patterns .

  • 1177

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

  • 4

    使用静态方法实现单例 . 进行单元测试的人员可以避免使用静态方法,因为它们不能被模拟或存根 . 该网站上的大多数人都是单元测试的重要支持者 . 通常最被接受的避免它们的惯例是使用inversion of control模式 .

  • 71

    Singleton与单个实例无关!

    与其他答案不同,我不想谈谈单身人士出了什么问题,而是向你展示他们使用时的强大和真棒!

    • Problem :Singleton在多线程环境中可能是一个挑战
      Solution :使用单线程引导过程初始化单例的所有依赖项 .

    • Problem :嘲笑单身人士很难 .
      Solution :使用方法Factory模式进行模拟
      MyModel myModel = Factory.inject(MyModel.class); 您可以将 MyModel 映射到继承它的 TestMyModel 类,无论何时注入 MyModel ,您都会获得 TestMyModel instread .

    • Problem :单身人士可能因为他们从未处理过而导致记忆韭菜 .
      Solution :好吧,处理它们!在您的应用中实施回调以正确处理单例,您应该删除链接到它们的所有数据,最后:从工厂中删除它们 .

    正如我在 Headers 中所述,单身人士不是单身实例 .

    • Singletons improves readability :您可以查看您的类,看看它注入了什么单例以找出它的依赖关系 .

    • Singletons improves maintenance :一旦你从一个类中删除了一个你刚删除了一些单例注入的依赖项,你就不需要去编辑其他类的大链接了,这些类只是移动了你的依赖项(这对我来说是臭臭的代码@Jim Burger

    • Singletons improves memory and performance :当你的应用程序发生某些事情,并且需要一长串回调来传递时,你就会浪费内存和性能,通过使用Singleton你正在削减中间人,并提高你的性能和内存使用率(避免不必要的局部变量分配) .

  • 65

    关于单身人士还有一件事,没有人说过 .

    在大多数情况下,"singletonity"是某些类的实现细节,而不是其接口的特征 . 控制容器的反转可能会隐藏类用户的这一特征;你只需要将你的类标记为单例(例如在Java中使用 @Singleton 注释)并且's it; IoCC will do the rest. You don't需要提供对单例实例的全局访问,因为访问权限已经由IoCC管理 . 因此IoC Singletons没有任何问题 .

    与IoC Singletons相反的GoF Singletons应该通过getInstance()方法在界面中暴露“单一性”,这样他们就会遭受上述所有事情的困扰 .

  • 13

    单身是一种模式,可以像任何其他工具一样使用或滥用 .

    单身人员的坏部分通常是用户(或者我应该说不适当地使用单身人士来做它不打算做的事情) . 最大的罪犯使用单身人士作为虚假的全球变量 .

  • 6

    单身人士解决了一个(也是唯一一个)问题 .

    Resource Contention.

    如果你有一些资源

    1 )只能有一个实例,而且

    2 )您需要管理该单个实例,

    你需要 singleton .

    没有很多例子 . 日志文件是最重要的 . 您不想只放弃单个日志文件 . 您想要正确刷新,同步和关闭它 . 这是必须管理的单个共享资源的示例 .

    它很糟糕的是,他们感觉像是global并且他们是GoF Design Patterns书的全额付清成员 .

    当你认为自己需要全局时,你可能会犯一个可怕的设计错误 .

  • 11

    单身者在 clustering 时也很糟糕 . 因为那样,你的应用程序中就不再有"exactly one singleton"了 .

    请考虑以下情况:作为开发人员,您必须创建一个访问数据库的Web应用程序 . 要确保并发数据库调用不会相互冲突,请创建线程保存 SingletonDao

    public class SingletonDao {
        // songleton's static variable and getInstance() method etc. omitted
        public void writeXYZ(...){
            synchronized(...){
                // some database writing operations...
            }
        }
    }
    

    因此,您确定应用程序中只存在一个单例,并且所有数据库都通过此单例并且只有 SingletonDao . 您的 生产环境 环境现在如下所示:
    Single Singleton

    到目前为止一切都很好 .

    现在,考虑您要在群集中设置Web应用程序的多个实例 . 现在,你突然间有这样的事情:

    Many singletons

    这听起来很奇怪,但是 now you have many singletons in your application . 而这正是单身人士不应该做的事情:有很多对象 . 如果您(如此示例中所示)想要对数据库进行同步调用,则这尤其糟糕 .

    当然,这是单身人士使用不当的一个例子 . 但是这个例子的信息是:你不能依赖应用程序中只有一个单例实例 - 特别是在集群方面 .

  • 105
    • 很容易(ab)用作全局变量 .

    • 依赖于单例的类相对更难以单独进行单元测试 .

  • 413

    当您使用单例编写代码时,例如 Logger 或数据库连接,然后您发现需要多个日志或多个数据库,您就遇到了麻烦 .

    单身人士很难从他们移动到常规物体 .

    此外,编写非线程安全的单例也太容易了 .

    您应该将所有需要的实用程序对象从函数传递给函数,而不是使用单例 . 如果将它们全部包装到辅助对象中,可以简化这一点,如下所示:

    void some_class::some_function(parameters, service_provider& srv)
    {
        srv.get<error_logger>().log("Hi there!");
        this->another_function(some_other_parameters, srv);
    }
    
  • 42

    Vince Huston有这些标准,这对我来说似乎是合理的:

    仅当满足以下所有三个条件时才应考虑单例:单个实例的所有权无法合理分配延迟初始化是合乎需要的全局访问不提供单个实例的所有权,何时以及如何进行初始化,以及全局访问不是问题,Singleton不够有趣 .

  • 20

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

  • 15

    我认为这种混乱是因为人们不够强调这一点 . Singleton是 not 一个包裹全局变量的模式 . 单例模式只应用于保证运行时 one and only one instance of a given class 存在 .

    人们认为Singleton是邪恶的,因为他们将它用于全局变量 . 正是由于这种混乱,单身人士被人瞧不起 . 请不要混淆单身人士和全球人士 . 如果用于它的目的,您将从Singleton模式中获得极大的好处 .

  • 7

    并不是单身人士自己很糟糕,而是GoF的设计模式 . 唯一真正有效的论点是GoF设计模式在测试方面不适用,特别是如果测试是并行运行的话 .

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

    • 确保将用作单例的类实现接口 . 这允许使用相同的接口实现存根或模拟

    • 确保Singleton是线程安全的 . 这是给定的 .

    • 单身人员应该是简单的,而不是过于复杂 .

    • 在应用程序的运行时期间,需要将单例传递给给定对象,使用构建该对象的类工厂并让类工厂将单例实例传递给需要它的类 .

    • 在测试期间并确保确定性行为,将单例类创建为单独的实例,作为实际类本身或实现其行为的存根/模拟,并将其原样传递给需要它的类 . 不要使用在测试期间创建需要单例的测试对象的类因子,因为它会传递它的单个全局实例,这会破坏目的 .

    我们在我们的解决方案中使用了Singletons,取得了巨大成功,可以测试并行测试运行流中的确定性行为 .

  • 14

    See Wikipedia Singleton_pattern

    一些人也认为它是一种反模式,他们认为它过度使用,在实际上不需要一个类的唯一实例的情况下引入了不必要的限制 . [1] [2] [3] [4]

    参考文献(仅文章中的相关参考文献)

  • 6

    单身人士并不坏 . 当你创造一个全球唯一并且不是全球唯一的东西时,这是唯一的坏处 .

    但是,有“应用程序范围服务”(考虑使组件交互的消息传递系统) - 这个CALLS用于单例,“MessageQueue” - 具有方法“SendMessage(...)”的类 .

    然后,您可以从所有地方执行以下操作:

    MessageQueue.Current.SendMessage(new MailArrivedMessage(...));

    当然,这样做:

    MessageQueue.Current.RegisterReceiver(本);

    在实现IMessageReceiver的类中 .

  • 6

    Monopoly is the devil and singletons with non-readonly/mutable state are the 'real' problem...

    按照jason's answer中的建议阅读了Singletons are Pathological Liars后,我发现了这个小小的花絮,它提供了最好的 how 单例的例子经常被滥用 .

    全球是坏的,因为:a . 它导致命名空间冲突b . 当谈到单身人士时,它以无根据的方式暴露国家 . 调用它们的显式OO方式可以防止冲突,所以指向a . 不是问题b . 没有国家的单身人士(像工厂)不是问题 . 有状态的单身人士可以再次分为两类,一类是不可变的或一次写入并且读取很多(配置/属性文件) . 这些都不错 . 作为参考持有者的可变单身人士就是你所说的那些人 .

    在最后一个声明中,他指的是博客的“单身人士是骗子”的概念 .

    How does this apply to Monopoly?

    要开始一场垄断游戏,首先:

    • 我们首先 Build 规则,以便每个人都在同一页面上

    • 在比赛开始时,每个人都有一个平等的开始

    • 只提出一套规则以避免混淆

    • 规则不允许在整个游戏中改变

    现在,对于没有垄断的人来说,这些标准至多是理想的 . 垄断的失败是难以接受的,因为垄断是关于金钱的,如果你失败了,你必须煞费苦心地观察剩下的球员完成比赛,而且损失通常是迅速和惨淡的 . 因此,规则通常会在某些时候扭曲,以牺牲其他人的利益为一些球员的自身利益服务 .

    所以你和朋友Bob,Joe和Ed一起垄断 . 您正迅速 Build 自己的帝国,并以指数的速度消耗市场份额 . 你的对手正在减弱,你开始嗅到血(比喻) . 你的好友鲍勃把他所有的钱都投入到尽可能多的低 Value 房产中,但他没有像他预期的那样获得高额投资回报 . 鲍勃,作为一个运气不好的中风,落在你的木板路上,并从游戏中被切除 .

    现在游戏从友好的骰子滚动到严肃的事业 . 鲍勃已经成为失败的榜样,乔和埃德不想最终像'那家伙' . 所以,作为领先的玩家,你突然变成了敌人 . Joe和Ed开始练习桌下交易,背后注入资金,低估房屋交换以及通常会削弱你作为玩家的任何东西,直到他们中的一个升到顶峰 .

    然后,不是其中一个获胜,而是从头开始 . 突然之间,一组有限的规则成为一个移动的目标,游戏退化为社交互动的类型,构成了自幸存者以来每一个高评级真人秀节目的基础 . 为什么,因为规则正在发生变化,并且没有就如何/为什么/它们应该代表什么达成共识,更重要的是,没有人做出决定 . 在那一点上,游戏中的每个玩家都在制定他/她自己的规则并且随后发生混乱,直到其中两个玩家太累而无法跟上游戏并慢慢放弃 .

    因此,如果游戏的规则手册准确地代表了单身人士,那么垄断规则手册将成为滥用的一个例子 .

    How does this apply to programming?

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

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

    A Singleton is only an option if you need what a singleton provides. A write-one read-only instance of an object. That same rule should cascade to the object's properties/members as well.

  • 30

    关于单身人士如何做坏事的答案总是“他们很难做对” . 语言的许多基础组件都是单例(类,函数,命名空间甚至运算符),计算的其他方面(localhost,默认路由,虚拟文件系统等)中的组件也是如此,并且不是偶然的 . 虽然它们不时会引起麻烦和挫折,但它们也可以使许多工作变得更好 .

    我看到的两个最大的问题是:将它视为全局并且无法定义Singleton闭包 .

    每个人都把单身人士称为全局,因为他们基本上都是 . 然而,全球范围内的许多(可悲的是,并非全部)不良并非本质上来自于全球性,而是如何使用它 . 单身人士也是如此 . 实际上更多,因为“单一实例”实际上并不需要意味着“全球可访问” . 它更像是一种天然的副产品,考虑到我们所知道的所有坏事,我们不应该急于利用全球可访问性 . 一旦程序员看到Singleton,他们似乎总是通过其实例方法直接访问它 . 相反,您应该像对待任何其他对象一样导航到它 . 大多数代码甚至不应该意识到它正在处理Singleton(松散耦合,对吧?) . 如果只有一小部分代码访问对象,就像它是全局的一样,那么很多伤害都会被撤消 . 我建议通过限制对实例函数的访问来强制执行它 .

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

    另一个不那么常见的错误就是忽略了Singleton的生活方式 . 仅仅因为只有一个并不意味着Singleton是一些无所不能的“永远是并且永远都会”,也不是一般所希望的(没有开头和结尾的对象违反了代码中的各种有用的假设,并且只应该被使用在最绝望的情况下 .

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

    可悲的是,而不是教导如何做单身人士的好例子 . 我们教导不好的例子,让程序员暂时使用它们,然后告诉他们这是一个糟糕的设计模式 .

  • 21

    当几个人(或团队)达到相似或相同的解决方案时,就会出现一种模式 . 很多人仍然使用原始形式的单身人士或使用工厂模板(在Alexandrescu的Modern C Design中进行了很好的讨论) . 管理对象生命周期的并发性和难度是主要障碍,前者可以按照您的建议轻松管理 .

    像所有选择一样,辛格尔顿有着相当大的起伏 . 我认为它们可以适度使用,特别是对于在应用程序生命周期中存活的对象 . 他们类似(可能是)全局的事实可能会引发纯粹主义者 .

  • 12

    从纯粹主义的观点来看,单身人士是坏人 .

    从实际的角度来看, a singleton is a trade-off developing time vs complexity .

    如果你知道你的应用程序不会改变那么多,那么它们就可以了 . 只要知道如果你的需求以一种意想不到的方式发生变化,你可能需要重构一下(在大多数情况下这是非常好的) .

    单身人士有时也会使unit testing变得复杂 .

  • 7

    没有什么本质上的错误模式,假设它被用于模型的某些方面,这是真正单一的 .

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

  • 4

    我想在接受的答案中提出4点,希望有人可以解释为什么我错了 .

    • 为什么隐藏代码中的依赖项不好?已经存在许多隐藏依赖项(C运行时调用,OS API调用,全局函数调用),并且易于查找单例依赖项(搜索instance()) .

    “制作一些全球性的东西以避免传递它是一种代码味道 . ”为什么不传递一些东西以避免使它成为代码气味的单身?

    如果你通过调用堆栈中的10个函数传递一个对象只是为了避免单例,那么这么好吗?

    • 单一责任原则:我认为这有点模糊,取决于您对责任的定义 . 一个相关的问题是,为什么要将这个特定的"responsibility"添加到一个类?

    • 为什么将一个对象传递给一个类使它比使用该对象作为一个单独的类更紧密耦合?

    • 为什么它会改变国家持续多久?可以手动创建或销毁单例,因此控件仍然存在,并且您可以使生命周期与非单例对象的生命周期相同 .

    关于单元测试:

    • 并非所有类都需要进行单元测试

    • 并非所有需要进行单元测试的类都需要更改单例的实现

    • 如果它们确实需要进行单元测试并且需要更改实现,则很容易将类从使用单例改为通过依赖注入将单例传递给它 .

相关问题