单例是一种备受争议的设计模式,所以我对 Stack Overflow 社区对它们的看法很感兴趣。
请为您的观点提供理由,而不仅仅是“单身人士适合懒惰的程序员!”
这是一篇关于这个问题的相当好的文章,虽然它反对使用单例: scientificninja.com:performant-singletons。
有没有人有其他关于他们的好文章?也许支持单身人士?
单例是一种备受争议的设计模式,所以我对 Stack Overflow 社区对它们的看法很感兴趣。
请为您的观点提供理由,而不仅仅是“单身人士适合懒惰的程序员!”
这是一篇关于这个问题的相当好的文章,虽然它反对使用单例: scientificninja.com:performant-singletons。
有没有人有其他关于他们的好文章?也许支持单身人士?
为单身人士辩护:
反对单身人士:
我自己的看法:
我使用单例,但如果有合理的选择,请避免使用它们。到目前为止,这对我来说效果很好,而且我发现它们是可测试的,尽管需要做更多的测试工作。
Google 有一个用于 Java 的Singleton Detector,我相信它最初是一个工具,必须在 Google 生成的所有代码上运行。删除单例的简单原因:
因为它们会使测试变得困难并隐藏设计中的问题
有关更明确的解释,请参阅 Google 的“为什么单身人士有争议”。
单例只是一堆花哨的全局变量。
全局变量和单例一样有它们的用途,但是如果您认为使用单例而不是使用令人讨厌的全局变量(每个人都知道全局变量是不好的),那么您会被误导。
单例的目的是确保一个类只有一个实例,并提供对它的全局访问点。大多数时候,重点是单个实例点。想象一下,如果它被称为 Globalton。这听起来不那么吸引人,因为这强调了(通常)全局变量的负面含义。
大多数反对单例的好论据都与它们在测试中存在的困难有关,因为为它们创建测试替身并不容易。
在 Google 测试博客中, Miško Hevery 写了三篇关于单例的非常好的博客文章。
单例模式并不是一个可怕的模式,尽管它被滥用了很多。我认为这种误用是因为它是一种更简单的模式,而且大多数单身人士都被全局副作用所吸引。
Erich Gamma曾说过,单例模式是他希望 GOF 书中没有包含的模式,这是一个糟糕的设计。我倾向于不同意。
如果在任何给定时间使用该模式来创建对象的单个实例,则该模式被正确使用。如果为了产生全局效果而使用单例,那么它的使用不正确。
缺点:
优点:
小鸡们喜欢我,因为我很少使用单例,而且当我这样做时,它通常是不寻常的。不,说真的,我喜欢单例模式。你知道为什么?因为:
当然,“专家”会抛出一堆关于“单元测试”和“依赖注入”的讨论,但这都是野狗的肾脏负担。你说单例很难进行单元测试?没问题!只需将所有内容都公开,然后将您的班级变成一个有趣的全球善良之家。你还记得 1990 年代的汉兰达节目吗?单例有点像这样,因为: A. 它永远不会死;B. 只能有一个。所以不要再听那些 DI 小鬼了,放弃实现你的单例。这里有一些更充分的理由......
我认为对于单例模式的使用存在很大的误解。这里的大多数评论都将其称为访问全局数据的地方。我们需要在这里小心——单例模式不是用于访问全局变量。
单例应该用于只有一个给定类的实例。Pattern Repository有很多关于Singleton的信息。
我曾与之共事的一位同事非常有单身主义思想。每当有什么像经理或老板这样的对象时,他都会把它变成一个单身人士,因为他认为应该只有一个老板。每次系统接受一些新要求时,结果证明有完全正当的理由允许多个实例。
如果域模型规定(不是“建议”)有一个,我会说应该使用单例。所有其他情况只是一个类的偶然的单个实例。
我一直在想办法在这里拯救可怜的辛格尔顿,但我必须承认这很难。我很少看到它们的合法用途,并且在当前驱动进行依赖注入和单元测试的情况下,它们很难使用。它们绝对是使用设计模式进行编程的“货物崇拜”表现形式。我曾与许多从未破解过“GoF”书但他们知道“Singelton”的程序员一起工作,因此他们知道“模式”。
不过,我确实不得不不同意 Orion,大多数时候我看到单人过度使用它不是连衣裙中的全局变量,而更像是连衣裙中的全局服务(方法)。有趣的是,如果您尝试通过 CLR 接口在安全模式下使用 SQL Server 2005 中的 Singeltons,系统将标记代码。问题是您拥有超出可能运行的任何给定事务的持久数据,当然,如果您将实例变量设置为只读,您可以解决这个问题。
这个问题导致了我一年的大量返工。
圣战!好的让我看看..上次我检查设计警察说..
单例是不好的,因为它们阻碍了自动测试——不能为每个测试用例重新创建实例。相反,逻辑应该在可以轻松实例化和测试的类 (A) 中。另一个类 (B) 应该负责约束创建。单一责任原则脱颖而出!应该是团队知识,您应该通过 B 访问 A - 一种团队约定。
我主要同意。。
许多应用程序要求某个类只有一个实例,因此只有一个类实例的模式很有用。但是该模式的实现方式存在差异。
有静态单例,其中类强制每个进程只能有一个类的实例(在 Java 中实际上每个 ClassLoader 一个)。另一种选择是只创建一个实例。
静态单例是邪恶的——一种全局变量。它们使测试变得更加困难,因为不可能完全隔离地执行测试。您需要复杂的设置和拆卸代码来在每次测试之间清理系统,并且很容易忘记正确清理某些全局状态,这反过来可能会导致测试中出现未指定的行为。
只创建一个实例是好的。您只需在程序启动时创建一个实例,然后将指向该实例的指针传递给需要它的所有其他对象。依赖注入框架使这很容易——您只需配置对象的范围,DI 框架将负责创建实例并将其传递给所有需要它的人。例如,在 Guice 中,您将使用 @Singleton 注释该类,并且 DI 框架将只创建该类的一个实例(每个应用程序 - 您可以在同一个 JVM 中运行多个应用程序)。这使测试变得容易,因为您可以为每个测试创建一个新的类实例,并让垃圾收集器在不再使用该实例时将其销毁。没有全局状态会从一个测试泄漏到另一个测试。
欲了解更多信息: 清洁代码会谈 - “全球状态和单身人士”
单例作为实现细节很好。单例作为接口或访问机制是一个巨大的 PITA。
不带参数返回对象实例的静态方法与仅使用全局变量略有不同。相反,如果一个对象通过构造函数或其他方法对传入的单例对象具有引用,那么实际创建单例的方式并不重要,整个模式也无关紧要。
单例的最大问题是它们使单元测试变得困难,特别是当您想要并行但独立地运行测试时。
第二个是人们通常认为带有双重检查锁定的延迟初始化是实现它们的好方法。
最后,除非您的单例是不可变的,否则当您尝试扩展应用程序以在多个处理器上的多个线程中运行时,它们很容易成为性能问题。在大多数环境中,竞争同步的成本很高。
这不仅仅是一堆花里胡哨的变量,因为它有几十个职责,比如与持久层通信以保存/检索有关公司的数据,处理员工和价格集合等。
我必须说,您并没有真正描述应该是单个对象的东西,而且除了数据序列化之外,它们中的任何一个都应该是一个单例,这是值得商榷的。
我可以看到至少 3 组我通常会在其中设计的类,但我倾向于更小、更简单的对象,它们可以很好地完成一组狭窄的任务。我知道这不是大多数程序员的天性。(是的,我每天都在研究 5000 行类的怪物,我特别喜欢有人写的 1200 行方法。)
我认为关键在于,在大多数情况下,您不需要单刀,而且通常只会让您的生活更加艰难。
单例有其用途,但在使用和公开它们时必须小心,因为它们太容易被滥用,难以真正进行单元测试,并且很容易基于两个相互访问的单例创建循环依赖。
然而,这很有帮助,因为当您想确保所有数据在多个实例之间同步时,例如,分布式应用程序的配置可能依赖单例来确保所有连接都使用相同的 up-to-日期数据集。
我发现你必须非常小心为什么决定使用单例。正如其他人所提到的,这与使用全局变量本质上是相同的问题。您必须非常谨慎,并考虑使用一个可以做什么。
很少使用它们,通常有更好的方法来做事。我遇到过这样的情况,我用一个单例做了一些事情,然后在我发现它使事情变得更糟之后(或者在我想出了一个更好、更理智的解决方案之后,我不得不筛选我的代码以将其取出) )
我已经多次将单例与Spring结合使用,并且不认为它是拐杖或懒惰的。
这种模式允许我为一堆配置类型值创建一个类,然后在我的 Web 应用程序的多个用户之间共享该特定配置实例的单个(非可变)实例。
就我而言,单例包含特定于该客户端的客户端配置条件——css 文件位置、数据库连接条件、功能集等。这些类通过 Spring 实例化和访问,并由具有相同配置的用户(即来自同一公司的 2 个用户)共享。* **我知道这种类型的应用程序有一个名称,但它在逃避我*
我觉得为应用程序的每个用户创建(然后是垃圾收集)这些“常量”对象的新实例会很浪费。
我正在阅读很多关于“Singleton”、它的问题、何时使用它等内容,这些是我到现在为止的结论:
单例的经典实现和真正的需求之间的混淆:只有一个类的实例!
实施起来通常很糟糕。如果您想要一个唯一的实例,请不要使用返回静态对象的静态 GetInstance() 方法的(反)模式。这使得一个类负责实例化自身的单个实例并执行逻辑。这打破了单一职责原则。相反,这应该由一个负责确保只存在一个实例的工厂类来实现。
它在构造函数中使用,因为它易于使用并且不能作为参数传递。这应该使用依赖注入来解决,这是实现良好且可测试的对象模型的绝佳模式。
不是TDD。如果您使用 TDD,则会从实现中提取依赖项,因为您希望测试易于编写。这使您的对象模型更好。如果您使用 TDD,则不会编写静态 GetInstance =)。顺便说一句,如果您考虑具有明确职责的对象而不是类,您将获得相同的效果 =)。
我真的不同意化装创意中的一堆全局变量。当用于解决正确的问题时,单例非常有用。让我给你一个真实的例子。
我曾经为我工作的地方开发了一个小软件,有些表格必须使用有关公司、员工、服务和价格的一些信息。在它的第一个版本中,每次打开表单时,系统都会不断地从数据库中加载数据。当然,我很快意识到这种方法不是最好的。
然后我创建了一个名为company的单例类,它封装了关于这个地方的所有内容,并且在系统打开时它已经完全填充了数据。
这不仅仅是一堆花里胡哨的变量,因为它有几十个职责,比如与持久层通信以保存/检索有关公司的数据,处理员工和价格集合等。
此外,它是一个固定的、系统范围的、易于访问的公司数据点。
编写普通的、可测试的、可注入的对象,让 Guice/Spring/whatever 处理实例化。严重地。
这甚至适用于缓存或单例的任何自然用例。没有必要重复编写代码来尝试强制执行一个实例的恐怖。让您的依赖注入框架处理它。(如果您还没有使用轻量级 DI 容器,我推荐 Guice)。
例如 Java 中的单例的一件可怕的事情是,在某些情况下,您最终可能会得到同一个单例的多个实例。JVM 基于两个元素唯一标识:一个类的完全限定名,以及负责加载它的类加载器。
这意味着同一个类可以由两个彼此不知道的类加载器加载,并且应用程序的不同部分将具有与之交互的此单例的不同实例。
单例非常有用,使用它们本身并不是一种反模式。然而,他们之所以声名狼藉,主要是因为他们强迫任何消费代码承认他们是单例的,以便与他们进行交互。这意味着如果您需要“取消单一化”它们,对您的代码库的影响可能非常显着。
相反,我建议要么将 Singleton 隐藏在工厂后面。这样,如果您将来需要更改服务的实例化行为,您可以只更改工厂而不是使用 Singleton 的所有类型。
更好的是,使用控制容器的反转!它们中的大多数允许您将实例化行为与类的实现分开。