设计模式-Singletons真的那么糟糕吗?

可能重复:
Singletons有什么不好?

可以理解,在某些情况下会滥用许多设计模式,就像妈妈总是说:“一件好事并不总是一件好事!”

我注意到这些天,我经常使用Singletons,我担心自己会滥用设计模式,并且越来越多地养成不良习惯。

我们正在开发一个Flex应用程序,该应用程序具有很大的层次结构数据结构,当用户对其进行操作时会保留在内存中。 用户可以按需加载,保存,更改和刷新数据。

该数据通过Singleton类进行集中,该类聚集了一些ArrayCollections,Arrays,value对象以及一些通过getter和setter公开的其他本机成员变量。

为了从应用程序中的任何地方获取对我们数据的引用,我们对整个Model.getInstance()方法进行了处理,我确定每个人都熟悉。 这确保了我们始终能够获得相同的数据副本,因为在设计时,我们曾说过在应用程序生存期内仅允许存在一个实例。

通过该中央数据存储库,例如,我们可以轻松地调度属性更改的事件,并且可以具有多个引用中央数据的UI组件,更新它们的显示以反映发生的数据更改。

到目前为止,这种方法是有效的,并且已证明对我们的情况非常实用。

但是,我发现在创建新类时我有点过于渴望。 诸如某个类应该是Singleton还是应该以其他方式进行管理(例如使用工厂)之类的问题有时会变得有些困难,并带有一些不确定性。

我在哪里画单身人士? 是否有确定何时使用Singleton以及何时远离它们的良好指南。

另外,有人可以推荐一本关于设计模式的好书吗?

josef.van.niekerk asked 2020-01-14T14:33:03Z
12个解决方案
109 votes

是的,单身人士很糟糕。 它们很糟糕,因为它们为您做的所有事情都结合了两个属性,每个属性在大约95%的时间内都是不好的。 (这意味着平均而言,单身人士的平均不良率为99.75%;)

根据GoF的定义,单例是一种数据结构,其:

  1. 授予对对象的全局访问权限,以及
  2. 强制对象只能存在一个实例。

首先通常被认为是一件坏事。 我们不喜欢全局变量。第二点比较微妙,但是通常,在任何情况下,这都不是强制实施的合理限制。

有时,只有一个对象的一个实例才有意义。 在这种情况下,您选择仅创建一个。 您不需要单身人士来实施它。

通常,即使只有一个实例是“合理的”,事实证明毕竟没有意义。 迟早,您将需要多个记录器。 或多个数据库。 否则您将不得不为每个单元测试重新创建资源,这意味着我们必须能够随意创建它们。 在我们了解后果之前,过早地从代码中删除了灵活性。

单例隐藏依赖关系并增加耦合(每个类都可能依赖于一个单例,这意味着除非我们也重用所有单例,否则该类不能在其他项目中重用),并且因为这些依赖关系不是立即可见的(作为函数/构造函数参数) ),我们不会注意到它们,通常在创建它们时就不会考虑它。 简单地拉入一个单例非常容易,它几乎充当了局部变量,因此,一旦它们存在,我们倾向于大量使用它们。 这使得它们几乎不可能再次被移除。 您最终可能不是使用意大利面条式代码,而是使用意大利面条式依赖图。 或早或晚,您的失控依赖项将意味着单身人士开始彼此依赖,然后在尝试初始化一个时,您将获得循环依赖项。

它们使单元测试变得异常困难。 (如何测试在单例对象上调用函数的函数?我们不希望执行实际的单例代码,但是如何防止这种情况发生?

是的,单身人士很糟糕。

有时,您确实想要一个全球化的公司。 然后使用全局而不是单例。

有时,非常非常非常罕见,您可能会遇到以下情况:创建一个类的多个实例是错误的,在不引起错误的情况下无法完成该操作。 (关于我能想到的唯一一种情况,即使有人为做作,也就是假设您代表某种硬件设备。您只有一个GPU,因此如果要将其映射到代码中的对象,它将 只能存在一个实例)。 但是,如果您发现自己处于这种情况下(再次强调一下,即多个实例导致严重错误的情况,而不仅仅是“我不能想到一个以上实例的任何用例”的情况),然后执行 该约束,但同时又不使对象在全局范围内可见。

在极少数情况下,这两个属性中的每一个都是有用的。 但是我无法想到将它们结合起来是一件好事的情况。

不幸的是,很多人已经想到“单身人士是OOP兼容的全局变量”。 不,他们不是。 除了引入其他完全不相关的问题之外,他们仍然遭受与全局问题相同的问题。 绝对没有理由比单纯的老式Global偏爱单例。

jalf answered 2020-01-14T14:35:02Z
38 votes

要记住的关键是设计模式仅仅是帮助您理解抽象概念的工具。 一旦有了这种理解,就将自己专门局限于书中的“食谱”是没有意义的,并且会损害您编写最适合自己目的的代码的能力。

就是说,阅读GoF之类的书会为您提供更多思考问题的方法,这样,当需要自己实施某些事情时,您将拥有更广泛的视角来解决问题。

在您的情况下,如果在每种情况下使用单例都有意义,那么就继续吧。 如果它“合适”并且您必须以笨拙的方式实现它,那么您需要提出一个新的解决方案。 强迫不完美的图案有点像在圆孔中锤击方形钉。

假设您说“这种方法很有效,并且证明对我们的情况非常实用”,我认为您做的还不错。

这是一些好书:

四本书的帮派-设计模式的经典书

首要设计模式-我听说有人建议这样做

mandaleeka answered 2020-01-14T14:33:40Z
14 votes

软件开发人员似乎大致分为两大阵营,这取决于他们是否偏爱理想的编码风格还是务实的编码风格:

  • 理想主义:切勿使用单例模式。
  • 务实:避免单例模式。

就个人而言,我赞成务实的态度。 有时,打破规则是有道理的,但前提是您真正了解自己在做什么并且愿意承担相关的风险。 如果您可以对以下有关特定用例的问题回答“是”,则单例模式可以带来一些实际好处。

  • 单例是否在您的应用程序外部? 数据库,排队服务和ESB都是单例模式的完全有效的宏示例。
  • 吻:整个应用程序限于2-3个内部单例吗?
  • DRY:这些单例本质上是全局的,因此将导致必须将引用深入到您应用程序中的几乎每个对象中吗? (例如,记录器或组件介体)?
  • 您的单身人士是否仅相互依赖和/或操作环境相互依赖?
  • 您是否已确保每个单例的启动和关闭顺序正确,包括内存管理注意事项? 例如,“大中央”风格的线程池可能需要在main()中具有实例Run()和Shutdown()方法,以便确保仅在其操作的对象处于有效状态时才能运行任务。
kgriffs answered 2020-01-14T14:36:00Z
9 votes

单例不会杀死程序,程序员会杀死程序。

像任何编程结构一样,如果使用得当,您也不会shoot脚。

推荐的书籍不错,但它们并不总是提供足够的背景知识,因此您可能会选择使用Singleton。

只有在需要多个实例的情况下发现Singleton是一个糟糕的选择时,这种体验才会出现,突然之间,将对象引用注入到各处时会遇到很多麻烦。

有时最好继续进行操作,并准备好对象引用,但是您完全使用Singleton的事实确实有助于确定如果必须将其重构为其他设计时将要遇到的问题的范围。 我认为这是一件非常好的事情:也就是说,根本没有一个班级(即使设计不佳),也可以使您看到班级变更的影响。

Cade Roux answered 2020-01-14T14:36:39Z
4 votes

我们已经开始了一个项目,在这个项目中我们基本上面临相同的问题,即如何访问模型,尤其是其根元素。 该项目不是Flex应用程序,而是剧本! 网络应用程序,但这并不重要。

在系统中只有一个唯一的对象很好,问题是如何访问它。 因此,有关单例的争论与依赖注入(DI)的概念以及如何获取对象有关。

DI的主要参数如下:

  • 可测试性和嘲讽
  • 将对象实例与使用分离(这可能导致生命周期管理)
  • 关注点分离

DI的可能方法是(请参阅Fowler的经典文章):

  • 在方法参数中传递对象
  • 服务定位器
  • DI框架

从这个角度来看,单例模式只是一种服务定位器,例如 Model.getInstance()

但是为了在将来发生更改时提供最大的灵活性,应该尽可能多地传递对唯一对象的引用,并仅在必要时使用Model.getInstance()获得。 这还将产生更干净的代码。

ewernli answered 2020-01-14T14:37:49Z
3 votes

在我看来,使用Singletons直接表明存在设计缺陷。 原因很简单,因为它们允许人们绕过C ++中内置的常规对象创建和销毁机制。 如果一个对象需要引用另一个对象,则它应该在构造时传递对其的引用,或者在内部创建该对象的新实例。 但是,当您使用单例时,您会明显混淆创建和拆卸周期。 一个相关的问题是,控制单例的生存期极为困难。 结果,许多包含通用单例实现的程序包也包含笨拙的对象生存期管理器等。 有时我想知道这些是否不仅仅用于管理单例。

基本上,如果需要在许多地方使用对象,则应在堆栈的最高公共点显式创建该对象,然后通过引用将该对象传递给使用它的每个人。 有时人们使用Singleton是因为他们在将多个args传递给新线程时遇到问题,但是不要为此而烦恼,显式定义线程args并将它们以相同的方式传递给新线程。 您会发现您的程序运行起来更加整洁,并且不会因为静态初始化依赖项或错误的拆卸而使您感到讨厌。

Andy B answered 2020-01-14T14:38:17Z
2 votes

单身人士当然还不错。 它们有其用途,其中一些很好。 单身人士的确会被经验不足的开发人员过度使用,因为它通常是他们了解的第一个设计专利,而且相当简单,因此他们将其随意放置在各处,而无需考虑其含义。

每次您要使用单例时,请尝试考虑为什么这样做,以及使用此模式的好处和缺点。

单例确实创建了一个全局可访问的“东西”集(数据或方法),我认为大多数人会同意使用太多的全局变量并不是一个好主意。 类和面向对象的重点是将事物分组到不同的区域,而不仅仅是将所有内容都放入一个庞大的全局空间中。

我发现我更喜欢单例的“模式”之一是将所需的对象从顶部向下传递。 我在应用程序初始化阶段创建了一次,然后将它们向下传递到所有需要访问它们的对象。 它模仿单例模式的“单个创建”部分,但没有“全局”部分。

单例的全部要点是它只应存在1个对象。 您提到了一组数据控制类。 也许考虑到实际上,在某些情况下,应用可能想要创建两组数据控制类,因此在此上强制执行单例并不完全正确。 相反,如果您在应用程序init上创建了这些数据类并将其传递给您,那么您将只能创建1个集,因为这是当前应用程序所需要的,但是您可能会在某个时候(如果需要第二个集) 您可以轻松创建它们。 此外,数据控件类是否应该真正可以从应用程序中的任何位置全局访问。 我认为不是,相反,应该只能从较低级别的数据访问层访问它们。

有人推荐了GOF书。 我会说,是的,这是一本很棒的书,但首先请尝试先找到一本有关通用体系结构的书,然后首先阅读有关2/3 / n层设计,封装,抽象和这类原理的书。 这将为您提供一个更扎实的基础,以帮助您理解GOF所谈论的模式的正确用法。

[编辑:单例变体另一个有用的时候是当您想要某个东西的单个访问点,但是实现细节实际上可能不止一件事。 调用者不需要知道,在幕后他们对单例对象的请求实际上是针对多个可用对象解决的,并且返回了一个。 我在想一个类似线程池的东西,用途在哪里,嘿,只要给我一个线程,我需要1,但我不在乎哪个]

Simon P Stevens answered 2020-01-14T14:39:08Z
2 votes

我知道这是一个旧线程,但是似乎没有人提到适合OP尝试执行的实际模式。 我认为他所描述的需求被称为中介者模式。 SourceMaking是一个学习/引用此类信息的绝佳站点。 绝对是我向人们介绍软件模式的地方。 同样,通常不要将任何设计模式固有地是好是坏的想法都纳入考虑范围,这是一个好主意。 它们都有其用途,只有技巧是何时何地使用它们。 对我来说,从来没有使用过Singletons的人不了解他们的用处。

tnicks answered 2020-01-14T14:39:30Z
0 votes

不,它们不一定坏。

对于一本书,您需要从经典开始。

Stu answered 2020-01-14T14:39:54Z
0 votes

单身人士并不“那么糟糕”。 如果您有很多相关的Singleton,并且可以使用Factory替换/合并其中的多个,而又不会丢失您关心的任何内容,那么您就应该这样做。

至于书籍,那是一个经典。

chaos answered 2020-01-14T14:40:19Z
0 votes

我以为单身人士很好。

Jeff Meatball Yang answered 2020-01-14T14:40:39Z
0 votes

Google似乎相信Singletons是个坏主意。

这并不是说Google所做的一切都是完美的,也不是说他们的每一个观点都是任何论点的结尾,但是他们甚至写出了Singleton检测器将其根除。 你自有主张。

duffymo answered 2020-01-14T14:41:04Z
translate from https://stackoverflow.com:/questions/1020312/are-singletons-really-that-bad