关于语言不可知:单身人士:好的设计还是拐杖?

Singletons: good design or a crutch?

单例是一个备受争议的设计模式,所以我对StackOverflow社区对它们的看法很感兴趣。

请为您的意见提供理由,而不仅仅是"单例是为懒惰的程序员准备的!"

这是一篇关于这个问题的相当好的文章,尽管它反对使用单件:科学忍者网:表演独角戏。

有人有其他好文章吗?也许是为了支持单身?


为保护独生子女:

  • 它们没有全局的糟糕,因为全局没有标准的强制初始化顺序,而且您很容易看到由于简单的或意外的依赖顺序而产生的不确定的错误。单件(假设它们是在堆上分配的)是在所有全局变量之后以及在代码中一个非常可预测的位置创建的。
  • 它们对于资源懒惰的/缓存系统非常有用,比如到低速I/O设备的接口。如果您智能地为一个缓慢的设备构建了一个单点接口,并且没有人调用它,那么您将不会浪费任何时间。如果另一段代码从多个地方调用它,那么您的singleton可以同时为这两个地方优化缓存,并避免任何双重查找。您还可以很容易地避免单例控制资源上的任何死锁情况。

针对单件:

  • 在C++中,没有一种很好的方法可以在单体之后自动清理。有一些解决方法,还有一些稍微有点老套的方法,但是没有简单的,通用的方法来确保始终调用单例的析构函数。这并不是很糟糕的记忆方式——为此,把它看成更多的全局变量。但是,如果您的singleton分配了其他资源(例如,锁定了一些文件),而不释放它们,则可能会很糟糕。

我自己的看法:

我用单件的,但如果有合理的选择,就要避免。到目前为止,这对我很有效,我发现它们是可测试的,尽管要测试的工作稍微多一些。


谷歌有一个Java的单侦测器,我相信它是一个必须在谷歌上生成的所有代码上运行的工具。移除单件的基本原因:

because they can make testing
difficult and hide problems with your
design

要获得更明确的解释,请参阅谷歌的"为什么单身汉有争议"。


一件单件衣服只是一个花式连衣裙中的一系列全局变量。

全局变量有它们的用途,就像单例变量一样,但是如果你认为你在用单例变量做一些很酷和有用的事情,而不是用一个糟糕的全局变量(每个人都知道全局变量是坏的,mmkay),你很不幸被误导了。


单实例的目的是确保类只有一个实例,并提供对它的全局访问点。大多数时候,焦点都集中在单个实例点上。想象一下,如果它被称为一个地球仪。这听起来不那么吸引人,因为这强调了(通常)全局变量的负面含义。

大多数反对单例的好论点都与测试中出现的困难有关,因为为它们创建测试双例并不容易。


有三篇很好的关于Mi单身的博客文章?在谷歌测试博客中。

  • 单身是病态的骗子
  • 单身汉都去哪儿了?
  • 单子的根本原因

  • 单身并不是一个可怕的模式,尽管它被滥用了很多。我认为这种误用是因为它是一种更简单的模式,而且对单子最新的模式是被全球副作用所吸引。

    埃里希伽玛曾说,单件是一个模式,他希望没有包括在GOF的书,这是一个糟糕的设计。我倾向于不同意。

    如果模式用于在任何给定时间创建对象的单个实例,那么模式将被正确使用。如果使用单件以产生全局效果,则说明使用不当。

    缺点:

    • 在调用单例的代码中,您耦合到一个类
      • 由于很难用模拟对象替换实例,因此会给单元测试带来麻烦。
      • 如果以后由于需要多个实例而需要重构代码,则比将singleton类传递给使用它的对象(使用接口)更痛苦

    优势:

    • 类的一个实例在任何给定的时间点上表示。
      • 按设计,你是强制执行
    • 实例是在需要时创建的
    • 全局访问是一个副作用


    小妞们喜欢我,因为我很少单身,而且当我单身的时候,这是很不寻常的。不,说真的,我喜欢单件模式。你知道为什么吗?因为:

  • 我懒惰。
  • 什么都不会出错。
  • 当然,"专家"们会围绕"单元测试"和"依赖性注射"展开一系列的讨论,但这些都是野狗的肾脏。你说单打很难进行单元测试?没问题!把所有的事情都公之于众,把你的班级变成一个全球善良的有趣之家。你还记得20世纪90年代的《高地人秀》吗?单身汉是这样的,因为:A。它永远不会死;B。只有一个。所以不要再去听那些琐碎的东西了,尽情地实现你的单身生活吧。以下是更多的好理由…

    • 每个人都在这样做。
    • 单身模式让你战无不胜。
    • 单声道与"win"(或"fun"(取决于你的口音)押韵。

    我认为关于使用单体模式有一个很大的误解。这里的大多数评论都将其称为访问全局数据的地方。我们在这里需要小心——单例模式不是用来访问全局的。

    应使用singleton只具有给定类的一个实例。模式存储库有关于singleton的大量信息。


    我的一个同事非常专一。每当有什么东西是经理或老板喜欢的对象,他会把它变成一个单身汉,因为他认为应该只有一个老板。每次系统提出一些新的需求时,都会发现允许多个实例的理由是完全正确的。

    我会说,如果域模型规定(而不是"建议")存在一个,那么应该使用singleton。所有其他情况都只是一个类的偶然的单个实例。


    我一直在想办法来拯救这个可怜的歌手,但我必须承认这很困难。我已经看到它们的合法使用非常少,而且在当前进行依赖注入和D单元测试的驱动下,它们很难使用。它们无疑是"货物崇拜"的表现,我曾与许多程序员合作,他们从未破解过"gof"书,但他们知道"singelton",因此他们知道"模式"。

    不过,我确实不同意猎户座的观点,大多数时候我都看到歌手过度使用服装并不是全局变量,而是更像是穿着服装的全局服务(方法)。有趣的是,如果您试图通过clr接口以安全模式在SQL Server 2005中使用singletons,系统将标记代码。问题是,除了可能运行的任何给定事务之外,您还有持久性数据,当然,如果将实例变量设置为只读,就可以解决这个问题。

    这一问题给我一年带来了大量的返工。


    圣战!好的,让我看看……上次我查设计警察说……

    单件测试很糟糕,因为它们阻碍了自动测试——不能为每个测试用例重新创建实例。相反,逻辑应该在一个类(A)中,可以很容易地实例化和测试。另一个类(B)应该负责约束创建。一劳永逸!应该是团队知识,你应该通过B访问一种团队约定。

    我基本同意……


    许多应用程序要求某个类只有一个实例,因此只有一个类实例的模式很有用。但是模式的实现方式也有不同。

    有静态单元格,其中类强制每个进程只能有一个类的实例(在Java中实际上每个类加载器一个)。另一种选择是只创建一个实例。

    静态单子是邪恶的-一种全局变量。它们使测试更加困难,因为不可能完全隔离地执行测试。您需要复杂的设置和分解代码来清理每个测试之间的系统,而且很容易忘记正确地清理某些全局状态,这反过来可能导致测试中出现未指定的行为。

    只创建一个实例很好。程序启动时只创建一个实例,然后将指向该实例的指针传递给所有其他需要它的对象。依赖注入框架使这变得容易——您只需配置对象的范围,DI框架将负责创建实例并将其传递给所有需要它的人。例如,在guice中,您将使用@singleton注释类,DI框架将只创建该类的一个实例(每个应用程序-您可以在同一个JVM中运行多个应用程序)。这使得测试变得容易,因为您可以为每个测试创建一个类的新实例,并让垃圾收集器在实例不再使用时销毁该实例。任何全局状态都不会从一个测试泄漏到另一个测试。

    更多信息:清洁代码会谈——"全球国家和单身汉"


    单例作为实现细节是可以的。作为接口或访问机制的singleton是一个巨大的PITA。

    不接受返回对象实例的参数的静态方法与仅使用全局变量略有不同。如果一个对象引用了通过构造函数或其他方法传入的singleton对象,那么实际上如何创建singleton并不重要,整个模式也不重要。


    It was not just a bunch of variables in a fancy dress because this was had dozens of responsibilities, like communicating with persistence layer to save/retrieve data about the company, deal with employees and prices collections, etc.

    我必须说,您并没有真正描述应该是一个单一对象的某个东西,而且有争议的是,除了数据序列化之外,它们中的任何一个都应该是一个单独的对象。

    我可以看到至少3组我通常会设计的类,但是我倾向于更小更简单的对象,它们可以很好地完成一组很窄的任务。我知道这不是大多数程序员的天性。(是的,我每天在5000个线类怪物上工作,我特别喜欢一些人写的1200线方法。)

    我认为关键是,在大多数情况下,你不需要歌手,通常你只是让你的生活更艰难。


    单件测试最大的问题是它们使单元测试变得困难,特别是当您希望并行但独立地运行测试时。

    第二个问题是,人们通常认为使用双重检查锁定的懒惰初始化是实现它们的一种好方法。

    最后,除非您的单例不可变,否则当您尝试将应用程序扩展到多个处理器上的多个线程中运行时,它们很容易成为性能问题。争用同步在大多数环境中都很昂贵。


    单件子有它们的用途,但是在使用和暴露它们时必须小心,因为它们太容易被滥用,很难真正进行单元测试,并且很容易基于两个相互访问的单件子创建循环依赖关系。

    但是,如果您希望确保所有数据都在多个实例之间同步,例如,分布式应用程序的配置可能依赖于单例来确保所有连接都使用相同的最新数据集,这是很有帮助的。


    我发现你必须非常小心为什么你决定用单胎。正如其他人提到的,它本质上与使用全局变量相同。你必须非常谨慎,考虑一下你可以用它来做什么。

    很少使用它们,而且通常有更好的方法来做事情。我遇到过这样的情况:我用一个单例程序做了一些事情,然后在我发现它使事情变得多么糟糕之后(或者在我想出了一个更好、更理智的解决方案之后),不得不筛选我的代码,把它取出来。


    我曾多次将单身汉与春天结合在一起,并没有把它当作拐杖或懒惰。

    这个模式允许我做的是为一组配置类型值创建一个类,然后在我的Web应用程序的几个用户之间共享该特定配置实例的单个(不可变)实例。

    在我的例子中,singleton包含特定于该客户机的客户机配置标准——CSS文件位置、DB连接标准、功能集等。这些类是通过Spring实例化和访问的,由具有相同配置的用户(即来自同一公司的2个用户)共享。***我知道这种类型的应用程序有一个名称,但它在逃避我。*

    我觉得为应用程序的每个用户创建(然后垃圾收集)这些"常量"对象的新实例是浪费的。


    我读了很多关于"singleton"的文章,它的问题,什么时候使用它,等等,直到现在我的结论是:

    • 单例实现的经典与实际需求之间的混淆:一个类只有一个实例!

    • 通常执行得不好。如果您想要一个唯一的实例,不要使用返回静态对象的静态getInstance()方法的(反)模式。这使得类负责实例化其自身的单个实例,并执行逻辑。这打破了单一责任原则。相反,这应该由一个工厂类实现,其职责是确保只存在一个实例。

    • 它在构造函数中使用,因为它易于使用,不能作为参数传递。这应该通过依赖注入来解决,这是实现一个好的、可测试的对象模型的一个很好的模式。

    • 不是TDD。如果您执行TDD,则从实现中提取依赖项,因为您希望测试易于编写。这使您的对象模型变得更好。如果使用TDD,则不会编写静态getInstance=)。顺便说一句,如果您考虑具有明确职责的对象而不是类,您将得到相同的效果=)。


    我真的不同意在一个花式服装想法中的一系列全局变量。单例在用来解决正确的问题时非常有用。让我给你一个真正的例子。

    我曾经在一个我工作的地方开发过一个小软件,有些表格必须使用一些关于公司、员工、服务和价格的信息。在第一个版本中,系统每次打开表单时都会从数据库中加载数据。当然,我很快意识到这种方法不是最好的。

    然后我创建了一个名为company的singleton类,它封装了关于这个地方的所有信息,在系统打开时它已经完全被数据填满了。

    这不仅仅是穿着花哨服装的一堆变数,因为它有许多职责,比如与持久层通信以保存/检索有关公司的数据、处理员工和价格收集等。

    另外,它是一个固定的、全系统的、易于访问的点,可以获得公司数据。


    JavaSon中的一件可怕的事情,例如Java,在某些情况下,你可以得到同一个单例的多个实例。JVM根据两个元素进行唯一标识:类的完全限定名和负责加载它的类加载器。

    这意味着同一个类可以由两个不知道彼此的类加载器加载,并且应用程序的不同部分将具有与它们交互的这个单例的不同实例。


    编写普通的、可测试的、可注入的对象,并让guice/spring/处理实例化。说真的。

    这甚至适用于缓存或任何单例的自然使用情况。没有必要重复编写代码来强制执行一个实例的恐怖行为。让您的依赖注入框架来处理它。(如果您还没有使用轻型DI容器,我建议您使用Guice)。


    单子非常有用,使用它们本身并不是一种反模式。然而,它们之所以声名狼藉,很大程度上是因为它们迫使任何消费代码承认它们是单例的,以便与它们进行交互。这意味着,如果您需要"取消单一色调"它们,对代码库的影响可能非常显著。

    相反,我建议要么把单身汉藏在工厂后面。这样,如果您将来需要更改服务的实例化行为,您可以只更改工厂,而不更改使用单例的所有类型。

    更好的是,使用反转控制容器!其中大多数允许您将实例化行为与类的实现分离。