单身人士:好的设计还是拐杖?

时间:2008-08-15 00:39:01

标签: language-agnostic design-patterns singleton

Singletons是一个备受争议的设计模式,所以我对Stack Overflow社区对它们的看法感兴趣。

请提供您的意见的理由,而不仅仅是“单身人士是懒惰的程序员!”

这是一篇关于这个问题的相当不错的文章,尽管它反对使用Singletons: scientificninja.com: performant-singletons

有没有人对它们有任何其他好文章?也许是为了支持Singletons?

23 个答案:

答案 0 :(得分:56)

为单身人士辩护:

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

反对单身人士:

  • 在C ++中,没有很好的方法可以在单身人士之后进行自动清理。有一些解决方法,并且有一些简单的方法可以做到,但是没有简单,通用的方法来制作确定你的单身人士的析构函数总是被调用。对于这个目的而言,这并不是一个非常可怕的记忆 - 只是将其视为更多的全局变量。但是,如果您的单例分配其他资源(例如锁定某些文件)并且不释放它们,则可能会很糟糕。

我自己的意见:

我使用单身,但如果有合理的选择,请避免使用它们。到目前为止,这对我来说效果很好,我发现它们是可测试的,虽然稍微需要测试一些。

答案 1 :(得分:37)

谷歌有一个Singleton Detector用于Java,我认为这是一个必须在谷歌生产的所有代码上运行的工具。删除单身人士的原因很简单:

  

因为他们可以进行测试   困难并隐藏你的问题   设计

有关更明确的说明,请参阅Google的“Why Singletons Are Controversial”。

答案 2 :(得分:31)

单身人士只是一件化装的全球变量。

全局变量有它们的用途,就像单身一样,但是如果你认为你正在用单例而不是使用令人讨厌的全局变量做一些很酷且有用的东西(每个人都知道全局变量是坏的mmkay),那么你不幸会被误导。

答案 3 :(得分:26)

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

反对单身人士的大多数好论据都与他们在测试中遇到的困难有关,因为为他们创造测试双打并不容易。

答案 4 :(得分:22)

Google测试博客中有{3}个关于Singletons的三篇非常好的博文。

      
  1. Miško Hevery
  2.   
  3. Singletons are Pathological Liars
  4.   
  5. Where Have All the Singletons Gone?

答案 5 :(得分:19)

Singleton 是一种可怕的模式,尽管误用很多。我认为这种误用是因为它是更简单的模式之一,并且单身人士的新手被全球副作用所吸引。

Erich Gamma说单身人士是他所希望的模式,不包括在GOF书中,这是一个糟糕的设计。我倾向于不同意。

如果使用模式以便在任何给定时间创建对象的单个实例,则正确使用该模式。如果使用单例以产生全局效果,则使用不正确。

缺点:

  • 您在整个调用单例的代码中耦合到一个类
    • 使用单元测试创​​建麻烦,因为很难用模拟对象替换实例
    • 如果由于需要多个实例而需要稍后重构代码,那么将单例类传递给使用它的对象(使用接口)会更加痛苦

优点:

  • 在任何给定的时间点表示一个类的一个实例。
    • 根据设计,您正在执行此
  • 需要时创建实例
  • 全球访问是一种副作用

答案 6 :(得分:16)

小鸡挖我,因为我很少使用单身,当我这样做时,通常是不寻常的。不,说真的,我喜欢单身人士模式。你知道为什么?这是因为:

  1. 我很懒。
  2. 什么都不会出错。
  3. 当然,“专家”会抛出一堆关于“单元测试”和“依赖注入”的讨论,但这完全是dingo的肾脏负荷。你说单身人士很难进行单元测试吗?没问题!只要宣布一切公开,就把你的班级变成一个充满全球善意的有趣之家。你还记得1990年代的Highlander节目吗?单身就是这样的,因为:A。它永远不会死;和B.只能有一个。因此,请停止倾听所有那些DI weenies并放弃实施您的单身人士。以下是一些更好的理由...

    • 每个人都在这样做。
    • 单身模式让你立于不败之地。
    • 单身人士以“胜利”(或“乐趣”取决于你的口音)押韵。

答案 7 :(得分:8)

我认为对Singleton模式的使用存在很大的误解。这里的大多数评论都将其称为访问全球数据的地方。我们在这里需要小心 - Singleton作为模式用于访问全局变量。

Singleton应该用于拥有给定类的只有一个实例Pattern Repository提供了有关Singleton的详细信息。

答案 8 :(得分:6)

我与之合作过的一位同事非常单身。每当有某种东西像管理者或老板那样对象时,他就会把它变成单身,因为他认为应该只有一个老板。每次系统采用一些新的要求时,事实证明允许多个实例是完全正确的理由。

我会说如果域模型指示(不是'建议')存在单身,则应该使用单身。所有其他情况都只是一个类的单个实例。

答案 9 :(得分:5)

圣战!好吧,让我看看..上次我检查了设计警察说..

单身人士很糟糕,因为他们阻碍了自动测试 - 无法为每个测试案例重新创建实例。 相反,逻辑应该在类(A)中,可以很容易地实例化和测试。另一个类(B)应该负责约束创建。单一责任原则脱颖而出!它应该是团队知识,你应该通过B访问A - 一种团队惯例。

我主要赞同......

答案 10 :(得分:5)

我一直试图想出一种方法来解决这里可怜的辛格尔顿的拯救问题,但我必须承认这很难。我已经看到它们的合法用途很少,并且当前的驱动器进行依赖注入和单元测试它们很难使用。他们肯定是编程设计模式的“货物崇拜”表现我曾与许多程序员合作过,他们从未破解过“GoF”书,但他们知道'Singelton',因此他们知道'模式'。

我不得不与Orion不同意,但大部分时间我都看到singeltons过度使用它不是服装中的全局变量,而更像是服装中的全球服务(方法)。有趣的是,如果您尝试通过CLR接口以安全模式在SQL Server 2005中使用Singeltons,系统将标记代码。问题是你有可能运行的任何给定事务之外的持久数据,当然如果你使实例变量只读,你可以解决问题。

这个问题导致我一年的很多返工。

答案 11 :(得分:4)

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

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

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

只创建一个实例 good 。您只需在程序启动时创建一个实例,然后将指向该实例的指针传递给需要它的所有其他对象。依赖注入框架使这变得简单 - 您只需配置对象的范围,DI框架将负责创建实例并将其传递给所有需要它的人。例如,在Guice中,您将使用@Singleton注释该类,并且DI框架将仅创建该类的一个实例(每个应用程序 - 您可以在同一JVM中运行多个应用程序)。这使得测试变得简单,因为您可以为每个测试创建类的新实例,并让垃圾收集器在不再使用它时销毁该实例。没有一个全球国家会从一个测试泄漏到另一个测试。

了解更多信息: The Clean Code Talks - "Global State and Singletons"

答案 12 :(得分:3)

Singleton作为实现细节很好。 Singleton作为一个接口或作为访问机制是一个巨大的PITA。

不带参数返回对象实例的静态方法与仅使用全局变量略有不同。相反,如果一个对象引用了传入的单例对象,无论是通过构造函数还是其他方法,那么单例实际创建的方式并不重要,整个模式也不重要。

答案 13 :(得分:2)

  

这不仅仅是一件奇装异服的变量,因为它有几十个职责,比如与持久层沟通以保存/检索有关公司的数据,处理员工和价格收集等等。

我必须说你并没有真正描述应该是一个单一对象的东西,而且除了数据序列化以外的任何一个都应该是一个singelton,这是有争议的。

我可以看到至少有3组我通常会设计的类,但我倾向于使用较小的简单对象来完成一组很窄的任务。我知道这不是大多数程序员的本质。 (是的,我每天都在处理5000线级的怪物,我特别喜欢有些人写的1200线方法。)

我认为重点在于,在大多数情况下,你不需要辛格尔顿,而且往往只是让你的生活变得更加艰难。

答案 14 :(得分:2)

单身人士面临的最大问题是他们进行单元测试 hard ,特别是当你想并行但独立地运行你的测试时。

第二,人们常常认为使用双重检查锁定进行延迟初始化是实现它们的好方法。

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

答案 15 :(得分:1)

我正在阅读很多关于“Singleton”,它的问题,何时使用它等等,这些是我的结论,直到现在:

  • Singleton的经典实现与真正的要求之间的混淆:只有一个类的实例!

  • 实施起来很糟糕。如果您想要一个唯一的实例,请不要使用返回静态对象的静态GetInstance()方法的(反)模式。这使得一个类负责实例化其自身的单个实例并且还执行逻辑。这打破了Single Responsibility Principle。相反,这应该由工厂类实现,负责确保只存在一个实例。

  • 它在构造函数中使用,因为它易于使用,不能作为参数传递。这应该使用dependency injection来解决,这是实现良好且可测试的对象模型的一种很好的模式。

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

答案 16 :(得分:1)

单身人士有其用途,但必须小心使用和暴露它们,因为它们是way too easy to abuse,难以真正进行单元测试,并且很容易根据相互访问的两个单例创建循环依赖关系

然而,当您想要确保所有数据在多个实例之间同步时(例如,分布式应用程序的配置)可能依赖于单例以确保所有连接使用相同的实例时,这是有用的。迄今为止的数据集。

答案 17 :(得分:1)

我和Spring一起使用了一些单身人士,并不认为这是一个拐杖或懒惰。

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

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

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

答案 18 :(得分:1)

我发现你必须非常小心为什么你决定使用单身人士。正如其他人所提到的,它与使用全局变量的问题基本相同。你必须非常谨慎,并考虑使用它可以做些什么。

使用它们非常罕见,通常有更好的方法可以做到。我遇到了一些情况,我已经用单例做了一些事情,然后我发现我的代码需要通过我的代码来筛选它,因为我发现它做了多么糟糕的事情(或者我提出了一个更好,更理智的解决方案之后) )

答案 19 :(得分:0)

编写普通的,可测试的,可注入的对象,让Guice / Spring /处理实例化。认真。

这甚至适用于高速缓存或单身人士的自然用例。 没有必要重复编写代码以试图强制执行一个实例的恐惧。让您的依赖注入框架处理它。 (如果您还没有使用Guice,我推荐使用Guice作为轻量级DI容器。)

答案 20 :(得分:0)

例如,Java中的单身人员可怕的一点是,在某些情况下,可以以同一单身的多个实例结束。 JVM基于两个元素进行唯一标识:类的完全限定名称,以及负责加载它的类加载器。

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

答案 21 :(得分:0)

单身人士非常有用,使用它们本身并不是一种反模式。然而,他们之所以声名狼借,主要是因为他们强迫任何消费代码承认他们是单身人士才能与他们互动。这意味着如果您需要“取消单一化”它们,对代码库的影响可能非常大。

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

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

答案 22 :(得分:0)

我真的不同意一套化装想法中的全局变量。单身人士在用于解决正确问题时非常有用。让我举个一个真实的例子。

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

然后我创建了一个名为 company 的单例类,它封装了有关该地点的所有内容,并且在系统打开时完全填满了数据。

这不只是一堆在奇装异服变量,因​​为这是对数十种责任的,就像持久层沟通保存/恢复有关该公司的数据,处理员工和价格收藏,等等。

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