2089

例模式GoF模式手册中完全付费的成员,但它最近似乎被开发人员世界孤立了。我仍然使用相当多的单例,特别是对于工厂类,虽然你必须对多线程问题(实际上就像任何类)有点小心,但我不明白为什么它们如此糟糕。

Stack Overflow 似乎特别假设每个人都同意单身人士是邪恶的。为什么?

请用“事实、参考资料或特定专业知识”来支持您的回答

4

36 回答 36

1381

来自布赖恩·巴顿的解释:

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

  2. 它们违反了单一责任原则:因为它们控制着自己的创建和生命周期。

  3. 它们固有地导致代码紧密耦合。在许多情况下,这使得在测试中伪造它们相当困难。

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

于 2008-09-26T06:13:44.630 回答
484

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

资源争用。

如果你有一些资源

( 1 ) 只能有一个实例,并且

( 2 ) 你需要管理那个单一的实例,

你需要一个单身人士

例子不多。日志文件很大。您不想仅仅放弃一个日志文件。您想正确刷新、同步和关闭它。这是必须管理的单个共享资源的示例。

很少需要单身人士。他们不好的原因是他们感觉像一个全球性的,并且他们是 GoF设计模式书的全额付费会员。

当你认为你需要一个全局变量时,你可能犯了一个严重的设计错误。

于 2008-09-26T23:11:19.387 回答
376

一些编码势利小人看不起他们,认为他们只是一个美化了的全球。就像许多人讨厌goto语句一样,还有其他人讨厌使用global的想法。我见过一些开发人员不遗余力地避免使用全局变量,因为他们认为使用全局变量是对失败的承认。奇怪但真实。

在实践中,单例模式只是一种编程技术,它是您的概念工具包中有用的一部分。有时您可能会发现它是理想的解决方案,因此请使用它。但是仅仅为了你可以吹嘘使用设计模式而使用它就像拒绝使用它一样愚蠢,因为它只是一个全局.

于 2008-09-26T06:13:11.090 回答
231

来自 Google 的 Misko Hevery 有一些关于这个主题的有趣文章……

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

单例只不过是全局状态。全局状态使您的对象可以秘密地获取未在其 API 中声明的内容,结果,单例使您的 API 成为病态的骗子。

所有的 Singletons Gone 都去哪儿了,这表明依赖注入使得将实例获取到需要它们的构造函数变得容易,这减轻了第一篇文章中所谴责的糟糕的全局 Singletons 背后的潜在需求。

于 2008-09-26T06:08:43.490 回答
138

我认为这种混乱是由于人们不知道单例模式的真正应用造成的。我怎么强调都不过分。单例不是包装全局变量的模式。单例模式应该只用于保证给定类的一个且只有一个实例在运行时存在。

人们认为 Singleton 是邪恶的,因为他们将它用于全局变量。正是因为这种混乱,Singleton 才被看不起。请不要混淆单例和全局。如果将其用于预期目的,您将从单例模式中获得极大的好处。

于 2008-09-26T18:03:07.553 回答
75

关于单例的一件相当糟糕的事情是你不能很容易地扩展它们。如果你想改变它们的行为,你基本上必须构建某种装饰器模式或类似的东西。此外,如果有一天你想用多种方法来做一件事,那么改变可能会很痛苦,这取决于你如何布置代码。

需要注意的一件事是,如果您确实使用单例,请尝试将它们传递给需要它们的人,而不是让他们直接访问它......否则,如果您选择有多种方式来做单例所做的事情,那将是如果每个类直接访问单例,则很难更改,因为每个类都嵌入了依赖项。

所以基本上:

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

而不是:

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

我相信这种模式被称为依赖注入,通常被认为是一件好事。

就像任何模式一样......想想它并考虑它在给定情况下的使用是否不合适......规则通常被打破,并且不应该随意应用模式而不加考虑。

于 2008-09-26T06:17:12.377 回答
70

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

从测试的角度来看,单例也是一个问题。它们倾向于使孤立的单元测试难以编写。控制反转(IoC) 和依赖注入是旨在以适合于单元测试的面向对象方式克服此问题的模式。

垃圾收集环境中,单例很快就会成为内存管理方面的问题。

在多线程场景中,单例可能会成为瓶颈和同步问题。

于 2008-09-26T06:37:29.147 回答
56

单例使用静态方法实现。进行单元测试的人会避免使用静态方法,因为它们不能被模拟或存根。这个网站上的大多数人都是单元测试的大力支持者。避免它们的普遍接受的约定是使用控制模式的反转。

于 2008-09-26T06:11:11.813 回答
46

单例在集群方面也很糟糕。因为那时,您的应用程序中不再有“完全一个单例”。

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

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

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

到目前为止一切都很好。

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

许多单身人士

这听起来很奇怪,但现在您的应用程序中有很多单例。这正是单例不应该的:拥有很多对象。如果您(如本例所示)想要对数据库进行同步调用,这尤其糟糕。

当然,这是单例使用不当的一个例子。但是这个例子的信息是:你不能相信你的应用程序中只有一个单例实例——尤其是在集群方面。

于 2013-02-22T14:34:10.683 回答
45
  1. 它很容易(ab)用作全局变量。
  2. 依赖单例的类相对难以单独进行单元测试。
于 2008-09-26T06:06:42.417 回答
35

垄断是魔鬼,具有非只读/可变状态的单身人士是“真正的”问题......

在阅读杰森的回答中建议的单身人士是病态的骗子之后,我遇到了这个小花絮,它提供了单身人士经常被滥用的最佳例子

全球不好,因为:

  • 一种。它会导致命名空间冲突
  • 湾。它以毫无根据的方式暴露了国家

说到单身人士

  • 一种。调用它们的显式 OO 方式可以防止冲突,所以点 a。不是问题
  • 湾。没有状态的单身人士(如工厂)不是问题。有状态的单例又可以分为两类,一类是不可变的或一次写入多次读取(配置/属性文件)。这些都不错。可变单例,它们是一种引用持有者,就是您所说的那些。

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

这如何适用于垄断?

要开始垄断游戏,首先:

  • 我们首先制定规则,以便每个人都在同一页面上
  • 每个人在游戏开始时都有平等的开始
  • 仅提供一组规则以避免混淆
  • 在整个游戏过程中不允许更改规则

现在,对于没有真正玩过垄断的人来说,这些标准充其量是理想的。垄断的失败是难以下咽的,因为垄断就是为了钱,如果你输了,你就必须苦苦地看着其他玩家完成游戏,而损失通常是迅速而毁灭性的。因此,规则通常会在某些时候被扭曲,以牺牲其他人的利益来满足一些玩家的自身利益。

所以你正在和朋友 Bob、Joe 和 Ed 玩垄断游戏。你正在迅速建立你的帝国并以指数级的速度消耗市场份额。你的对手正在变弱,你开始闻到血腥味(象征性地)。你的好友鲍勃把他所有的钱都投入到了尽可能多的低价值房产上,但他并没有像他预期的那样获得高投资回报。不幸的是,鲍勃落在了您的木板路上并被从游戏中剔除。

现在游戏从友好的掷骰子变成了严肃的生意。Bob 已经成为失败的榜样,而 Joe 和 Ed 不想像“那个人”那样结束。所以,作为主角的你,突然之间,变成了敌人。Joe 和 Ed 开始练习暗中交易、背后注资、被低估的房屋交换以及通常会削弱你作为玩家的任何东西,直到其中一个上升到顶峰。

然后,不是其中一个获胜,而是整个过程重新开始。突然之间,一组有限的规则变成了一个移动的目标,游戏退化为社交互动类型,这将构成自幸存者以来每一个高收视率的真人秀节目的基础。为什么,因为规则正在发生变化,对于它们应该如何/为什么/代表什么没有达成共识,更重要的是,没有一个人做出决定。那时,游戏中的每个玩家都在制定他/她自己的规则,混乱接踵而至,直到两个玩家太累而无法继续游戏并慢慢放弃。

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

这如何适用于编程?

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

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

如果您需要单例提供的功能,单例只是一种选择对象的只写只读实例。同样的规则也应该级联到对象的属性/成员。

于 2010-06-25T00:33:49.120 回答
34

单例不是关于单实例!

与其他答案不同,我不想谈论单例有什么问题,而是要向您展示它们在正确使用时是多么强大和真棒!

  • 问题:单例在多线程环境中可能是一个挑战
    解决方案:使用单线程引导进程来初始化单例的所有依赖项。
  • 问题:很难模拟单例。
    解决方案:使用方法工厂模式进行模拟

您可以映射MyModelTestMyModel继承它的类,无论何时MyModel注入您都会得到TestMyModelinstread。-问题:单例可能会导致内存泄漏,因为它们从未处理过。
解决方案:好吧,处理它们!在您的应用程序中实现回调以正确处理单例,您应该删除链接到它们的所有数据,最后:将它们从工厂中删除。

正如我在标题中所说的,单例与单例无关。

  • 单例提高了可读性:您可以查看您的类并查看它注入的单例以找出它的依赖项。
  • 单例改进了维护:一旦你从一个类中删除了一个依赖项,你就删除了一些单例注入,你不需要去编辑其他类的大链接,这些类只是移动了你的依赖项(这对我来说是臭代码@Jim Burger )
  • 单例提高内存和性能:当您的应用程序中发生某些事情时,需要一长串的回调来传递,您正在浪费内存和性能,使用单例您正在削减中间人,并提高您的性能和内存使用(通过避免不必要的局部变量分配)。
于 2015-11-08T14:54:16.197 回答
24

参见维基百科 Singleton_pattern

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

参考资料(仅来自文章的相关参考资料)

  1. ^ 亚历克斯·米勒。我讨厌的模式 #1:Singleton,2007 年 7 月
  2. ^ 斯科特登斯莫尔。为什么单身人士是邪恶的,2004 年 5 月
  3. ^ 史蒂夫·耶格。单身人士被认为是愚蠢的,2004 年 9 月
  4. ^ JB Rainsberger,IBM。明智地使用你的单身人士,2001 年 7 月
于 2008-09-26T06:07:32.820 回答
23

我对单身人士有多糟糕的回答总是“他们很难做对”。语言的许多基础组件都是单例(类、函数、命名空间甚至运算符),计算的其他方面(本地主机、默认路由、虚拟文件系统等)中的组件也是如此,这并非偶然。虽然它们不时造成麻烦和沮丧,但它们也可以使很多事情变得更好。

我看到的两个最大的错误是:将其视为全局和未能定义 Singleton 闭包。

每个人都将 Singleton 称为全局变量,因为它们基本上是。然而,全局中的许多(可悲的是,不是全部)坏处本质上不是来自全局,而是你如何使用它。单身人士也是如此。实际上,“单实例”实际上并不一定意味着“全局可访问”。它更像是一种自然的副产品,鉴于我们所知道的所有坏事都来自于它,我们不应该这么急于利用全球可访问性。一旦程序员看到一个单例,他们似乎总是直接通过它的实例方法访问它。相反,您应该像导航任何其他对象一样导航到它。大多数代码甚至不应该知道它正在处理单例(松散耦合,对吗?)。如果只有一小部分代码像访问全局对象一样访问该对象,则可以消除很多危害。

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

另一个不太常见的错误是忽略了单身人士的生活方式。仅仅因为只有一个并不意味着 Singleton 是某种无所不能的“一直是并且将永远是”,也不是通常可取的(没有开始和结束的对象违反了代码中的各种有用假设,并且应该只使用在最绝望的情况下。

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

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

于 2013-05-29T02:35:49.680 回答
17

文斯休斯顿有这些标准,在我看来是合理的:

仅当满足以下所有三个条件时才应考虑单例:

  • 无法合理分配单个实例的所有权
  • 延迟初始化是可取的
  • 未另外提供全球访问权限

如果单个实例的所有权、初始化发生的时间和方式以及全局访问都不是问题,那么 Singleton 就不够有趣。

于 2009-11-23T09:58:11.857 回答
17

并不是单例本身不好,而是 GoF 设计模式不好。唯一真正有效的论点是 GoF 设计模式不适用于测试,尤其是在并行运行测试时。

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

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

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

  3. 单例在本质上应该是简单的,而不是过于复杂。

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

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

我们在我们的解决方案中使用了单例,并取得了很大成功,这些可测试确保并行测试运行流中的确定性行为。

于 2012-08-06T01:46:05.137 回答
16

我想解决已接受答案中的 4 点,希望有人能解释我为什么错了。

  1. 为什么在代码中隐藏依赖项不好?已经有几十个隐藏依赖(C 运行时调用、OS API 调用、全局函数调用),单例依赖很容易找到(搜索 instance())。

    “使某些东西全局化以避免传递它是一种代码味道。” 为什么不传递一些东西以避免使其成为单例代码气味?

    如果您通过调用堆栈中的 10 个函数传递一个对象只是为了避免单例,那是不是很棒?

  2. 单一职责原则:我认为这有点模糊,取决于您对职责的定义。一个相关的问题是,为什么将这种特定的“责任”添加到班级很重要?

  3. 为什么将对象传递给类比在类中使用该对象作为单例更紧密耦合?

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

关于单元测试:

  • 并非所有类都需要进行单元测试
  • 并非所有需要进行单元测试的类都需要更改单例的实现
  • 如果它们确实需要进行单元测试并且确实需要更改实现,则可以很容易地将类从使用单例更改为通过依赖注入将单例传递给它。
于 2010-04-07T13:31:48.510 回答
14

从纯粹主义者的角度来看,单身人士是不好的。

从实用的角度来看,单例是开发时间与复杂性的权衡

如果您知道您的应用程序不会发生太大变化,那么可以使用它们。只要知道如果您的需求以意想不到的方式发生变化(在大多数情况下这很好),您可能需要重构一些东西。

单例有时也会使单元测试复杂化。

于 2011-03-06T18:02:27.880 回答
13

假设该模式被用于模型的某些方面,它是真正单一的,那么该模式本身并没有错。

我相信这种强烈反对是由于它的过度使用,而这反过来又是因为它是最容易理解和实施的模式。

于 2008-09-26T06:07:41.870 回答
13

我不打算评论好/坏的论点,但自从Spring出现以来我就没有使用过它们。使用依赖注入几乎消除了我对单例、服务定位器和工厂的要求。我发现这是一个更高效、更干净的环境,至少对于我所做的工作类型(基于 Java 的 Web 应用程序)而言。

于 2008-09-26T06:46:29.577 回答
11

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

单例的坏部分通常是用户(或者我应该说不恰当地使用单例来完成它不适合做的事情)。最大的违规者是使用单例作为虚假的全局变量。

于 2008-09-26T17:24:45.053 回答
9

当您使用单例编写代码时,例如记录器或数据库连接,然后您发现您需要多个日志或多个数据库,您就有麻烦了。

单例很难从它们转移到常规对象。

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

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

void some_class::some_function(parameters, service_provider& srv)
{
    srv.get<error_logger>().log("Hi there!");
    this->another_function(some_other_parameters, srv);
}
于 2008-09-26T06:20:10.263 回答
8

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

于 2008-10-09T08:18:15.497 回答
8

单例的问题是范围增加和耦合问题。不可否认,在某些情况下您确实需要访问单个实例,并且可以通过其他方式完成。

我现在更喜欢围绕控制反转(IoC) 容器进行设计,并允许容器控制生命周期。这使您受益于依赖于实例的类不知道存在单个实例的事实。将来可以更改单例的生命周期。我最近遇到的一个这样的例子是从单线程到多线程的简单调整。

FWIW,如果当您尝试对其进行单元测试时它是 PIA,那么当您尝试调试、错误修复或增强它时,它就会进入 PIA。

于 2009-04-28T00:20:32.397 回答
7

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

于 2008-09-26T06:55:50.490 回答
7

单身人士还不错。只有当您制作并非全球唯一的全球独一无二的东西时,这才是糟糕的。

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

然后,您可以从各地执行以下操作:

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

而且,当然,做:

MessageQueue.Current.RegisterReceiver(this);

在实现 IMessageReceiver 的类中。

于 2010-06-05T10:42:47.853 回答
6

这是关于单身人士的另一件事,还没有人说。

在大多数情况下,“单一性”是某个类的实现细节,而不是其接口的特征。控制容器的反转可能会向类用户隐藏此特性;您只需要将您的类标记为单例(@Singleton例如在 Java 中使用注释),仅此而已;IoCC 将完成剩下的工作。您无需提供对单例实例的全局访问权限,因为该访问权限已由 IoCC 管理。因此,IoC Singletons 没有任何问题。

与 IoC 单例相反的 GoF 单例应该通过 getInstance() 方法在接口中暴露“单例”,因此它们会遭受上述所有内容的影响。

于 2010-06-01T21:29:40.870 回答
5

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

于 2008-09-26T06:05:36.397 回答
5

首先,一个班级及其合作者应该首先完成他们的预期目的,而不是专注于依赖者。生命周期管理(当实例被创建和超出范围时)不应该是类责任的一部分。公认的最佳实践是制作或配置一个新组件以使用依赖注入来管理依赖关系。

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

不应将任何类视为单例,而应将其视为其用法的应用程序或如何使用它来配置依赖项。对于快速和讨厌的这无关紧要 - 只是卢克硬编码说文件路径无关紧要,但对于更大的应用程序,需要使用 DI 以更合适的方式分解和管理此类依赖关系。

单例在测试中引起的问题是他们硬编码的单一用例/环境的症状。测试套件和许多测试都是单独的,并且将与硬编码单例不兼容的东西分开。

于 2009-04-28T01:14:39.850 回答
5

单身人士不是邪恶的,如果你正确地最低限度地使用它。还有很多其他好的设计模式在某些时候取代了单例的需求(并且也给出了最好的结果)。但是一些程序员没有意识到这些好的模式并且在所有情况下都使用单例,这使得单例对他们来说是邪恶的。

于 2013-11-10T12:11:47.113 回答
4

当几个人(或团队)得出相似或相同的解决方案时,就会出现一种模式。很多人仍然使用原始形式的单例或使用工厂模板(Alexandrescu 的 Modern C++ Design 中有很好的讨论)。并发性和管理对象生命周期的困难是主要障碍,正如您所建议的那样,前者很容易管理。

像所有选择一样,Singleton 也有其相当的起伏。我认为它们可以适度使用,特别是对于在应用程序生命周期中存活的对象。它们类似于(并且可能是)全局变量的事实大概已经引起了纯粹主义者的注意。

于 2008-09-26T06:14:25.020 回答
3

Singleton –反模式!Mark Radford (Overload Journal #57 – Oct 2003) 是一本很好的读物,解释了为什么 Singleton 被认为是一种反模式。本文还讨论了替代 Singleton 的两种替代设计方法。

于 2008-09-26T06:21:02.883 回答
3

作者的一些反驳:

如果您将来需要使该类不再是单一的,那么您将被卡住一点也不 - 我在这种情况下有一个单一的数据库连接单例,我想将它变成一个连接池。请记住,每个单例都是通过标准方法访问的:

MyClass.instance

这类似于工厂方法的签名。我所做的只是更新实例方法以从池中返回下一个连接——不需要其他更改。如果我们没有使用单例,那将更加困难。

单例只是花哨的全局变量 无法反驳,但所有静态字段和方法也是如此 - 从类而不是实例访问的任何内容本质上都是全局的,我没有看到对使用静态字段的太多反对?

并不是说单身人士很好,只是在这里反驳一些“传统智慧”。

于 2008-10-25T06:12:59.397 回答
3

这是我认为到目前为止的答案中缺少的内容:

如果每个进程地址空间都需要这个对象的一个​​实例(并且你确信这个要求不会改变),你应该让它成为一个单例。

否则,它不是单例。

这是一个非常奇怪的要求,用户几乎不会感兴趣。进程和地址空间隔离是一个实现细节。它们仅在用户想要停止使用您的应用程序kill或任务管理器时才会影响用户。

除了构建一个缓存系统之外,没有太多理由可以让您如此确定每个进程应该只有一个实例。日志系统怎么样?对于每个线程或更细粒度的处理可能会更好,这样您就可以更自动地跟踪消息的来源。应用程序的主窗口怎么样?这取决于; 也许您出于某种原因希望所有用户的文档都由同一进程管理,在这种情况下,该进程中将有多个“主窗口”。

于 2009-07-12T07:47:55.840 回答
2

它模糊了关注点的分离

假设您有一个单例,您可以从类中的任何位置调用此实例。你的班级不再像它应该的那样纯粹。您的类现在将不再对其成员及其明确接收的成员进行操作。这会造成混乱,因为类的用户不知道类需要什么足够的信息。封装的整个想法是向用户隐藏方法的运行方式,但是如果在方法内部使用单例,则必须知道单例的状态才能正确使用该方法。这是反OOP的。

于 2008-09-26T06:10:34.140 回答
1

在我的头顶上:

  1. 他们强制执行紧耦合。如果您的单例与它的用户驻留在不同的程序集上,那么如果没有包含单例的程序集,则使用程序集将无法运行。
  2. 它们允许循环依赖,例如,程序集 A 可以有一个依赖于程序集 B 的单例,而程序集 B 可以使用程序集 A 的单例。所有这些都不会破坏编译器。
于 2008-09-26T06:33:47.877 回答