Are Singletons really that bad?
Possible Duplicate:
What is so bad about Singletons?
很多设计模式在某些情况下会被滥用,这是可以理解的,就像妈妈总是说的:"过多的好东西并不总是好的!"
我注意到最近,我经常使用单例,我担心自己可能滥用了设计模式,并且越来越深入地养成了一种坏习惯。
我们正在开发一个灵活的应用程序,当用户处理它时,它有一个很大的层次结构数据结构保存在内存中。用户可以根据需要加载、保存、更改和刷新数据。
这个数据通过一个singleton类集中起来,该类聚合了几个arraycollection、数组、值对象以及通过getter和setter公开的一些其他本机成员变量。
为了从应用程序中的任何地方获取对数据的引用,我们执行了整个model.getInstance()方法类型的操作,我确信每个人都熟悉。这确保了我们总是能得到相同的数据副本,因为当我们设计时,我们说在应用程序生命周期中只允许存在一次实例。
从这个中央数据存储库中,我们可以很容易地调度属性更改事件,并且可以有多个引用中央数据的UI组件,更新它们的显示以反映已经发生的数据更改。
到目前为止,这种方法已经被证明是有效的,并且在我们的环境中非常实用。
不过,我发现在创建新课程时,我有点过了头。像是一个类应该是一个单独的类,还是应该以其他方式管理它,例如使用一个工厂,这些问题有时会变得有点困难,并且有点不确定。
我在哪里用单件画这条线?有没有一个很好的指导方针来决定何时使用单件和何时远离它们。
还有,有人能推荐一本关于设计模式的好书吗?
是的,单身是不好的。它们是坏的,因为它们为您所做的只是将两个属性组合在一起,每一个属性在95%的时间里都是坏的。(这意味着,平均而言,单件产品的不良率为99.75%;)
GOF定义的单例是一种数据结构,它:
第一个通常被认为是一件坏事。我们不喜欢全球性的。第二种是稍微微妙一点,但一般来说,几乎没有任何情况下这是一个合理的强制执行限制。
有时,只有一个对象实例才有意义。在这种情况下,您选择只创建一个。你不需要一个单身汉来执行它。
通常,即使只有一个实例是"有意义的",结果还是没有意义。迟早,你需要不止一个记录器。或多个数据库。或者您必须为每个单元测试重新创建资源,这意味着我们必须能够随意创建它们。在我们理解结果之前,它过早地从代码中消除了灵活性。
singleton隐藏依赖项并增加耦合(每个类都可能依赖于singleton,这意味着除非我们也重用所有的singleton,否则类不能在其他项目中重用),并且由于这些依赖项不立即可见(作为函数/构造函数参数),因此我们不会注意到它们,并且通常不会认为当我们创造它们的时候。简单地拉入一个单例是很容易的,它几乎充当一个局部变量和所有变量,所以我们倾向于在它们存在的时候使用它们。这使得它们几乎不可能再被移除。你最终得到的也许不是意大利面代码,而是意大利面依赖关系图。迟早,失控的依赖关系将意味着单例开始依赖于彼此,然后在尝试初始化一个单例时获得循环依赖关系。
它们使得单元测试非常困难。(如何测试在单例对象上调用函数的函数?我们不希望执行实际的单例代码,但是如何防止这种情况发生呢?
是的,单身是不好的。
有时候,你真的想要一个全球性的。然后使用一个全局的,而不是单个的。
有时,非常罕见,您可能遇到这样一种情况,即创建一个类的多个实例是一个错误,在这种情况下,如果不引起错误,就无法完成该操作。(我能想到的唯一情况是,即使是人为的,如果你代表的是一些硬件设备。您只有一个GPU,所以如果您要将它映射到代码中的一个对象,那么只存在一个实例是有意义的)。但是,如果您发现自己处于这样一种情况下(再次强调,一种情况是多个实例导致严重错误,而不仅仅是"我无法为多个实例考虑任何用例"),那么请强制执行该约束,但不要同时使对象全局可见。
这两种性质在极少数情况下都是有用的。但我想不出一个单一的案例,它们的结合将是一件好事。
不幸的是,很多人都有这样的想法,"单身者是符合OOP标准的全球化者。"不,他们不是。除了引进其他一些完全不相关的问题外,他们还面临着与全球市场相同的问题。绝对没有理由比一个普通的旧全球更喜欢单身。
要记住的关键是,设计模式只是帮助您理解抽象概念的一个工具。一旦你有了这样的理解,把你自己具体地限制在一本书中的"食谱"上是毫无意义的,并且会损害你编写最适合自己目的的代码的能力。
也就是说,读一些像gof这样的书会给你提供更多的思考问题的方法,这样当你自己去实现某件事情的时候,你会有一个更广阔的视角来解决这个问题。
在您的情况下,如果在每种情况下使用singleton都是有意义的,那么就直接开始吧。如果它"某种程度上"合适,并且您必须以某种笨拙的方式实现它,那么您需要想出一个新的解决方案。强迫一个不完美的模式有点像在圆孔中锤一个方形的钉子。
考虑到你说"这种方法对我们的环境非常有效和实用",我认为你做得很好。
以下是一些好书:
《四人帮》设计图案经典之作
头先设计模式-我听过一些人推荐这种模式作为替代方案
软件开发人员似乎相当平均地分成两个阵营,这取决于他们喜欢理想化的编码风格还是实用的编码风格:
- 理想主义:永远不要使用单子模式。
- 务实:避免单件模式。
就我个人而言,我赞成务实的做法。有时打破规则是有意义的,但前提是你真正了解自己在做什么,并且愿意接受相关的风险。如果您可以回答下面关于您的特定用例的问题"是",那么单例模式可以产生一些实际的好处。
- 单件是你的应用程序外部的吗?数据库、队列服务和ESB都是单例模式的完全有效的宏示例。
- 基斯:你整个应用程序只限于2-3个内部单件?
- Dry:这些单例程序本身是全局的,因此会导致应用程序中几乎每个对象都有引用吗?(例如,记录器或组件调解器)?
- 您的单件产品是否只依赖于彼此和/或操作环境?
- 您是否确保了每个单例的正确启动和关闭顺序,包括内存管理注意事项?例如,一个"Grand Central"风格的线程池可能需要在main()中有instance run()和shutdown()方法,这样才能保证任务只有在其操作的对象处于有效状态时才能运行。
单例不杀死程序,程序员杀死程序。
像任何编程结构一样,如果使用得当,您将不会向自己的脚开枪。
推荐的书很好,但它们并不总能提供足够的背景,而这些背景往往与你可能选择使用单件的经验有关。
只有当您发现需要多个实例时,单例是一个糟糕的选择,而突然间,在任何地方注入对象引用时都会遇到很多问题。
有时最好继续进行,并将对象引用放在适当的位置,但是如果必须将其重构为其他设计,那么使用singleton的事实确实有助于确定您将面临的问题的范围。我认为这是一件很好的事情:也就是说,只要有一个班级(即使设计不好),就可以看到班级变化的影响。
我们已经启动了一个项目,在这个项目中,我们基本上面临着相同的问题,即如何访问模型,尤其是它的根元素。这个项目不是一个灵活的应用程序,而是一个游戏!网络应用,但这并不重要。
在系统中有一个唯一的对象是可以的,问题是如何访问它。因此,关于单例的争论与依赖注入(DI)的概念以及如何获得对象有关。
DI的主要参数如下:
- 可测试性和模拟
- 将对象实例化与使用分离(这可能导致生命周期管理)
- 关注点分离
DI的可能方法是(参见Fowler的经典文章):
- 在方法参数中传递对象
- 服务定位
- DI框架
从这个角度来看,单例模式只是一种服务定位器,例如
但是为了在将来的变化中提供最大的灵活性,应该尽可能多地传递对唯一对象的引用,并且只有在必要时才使用
在我看来,使用单件直接标志着一个设计缺陷。原因很简单,它们允许一个绕过C++中构建的正常对象创建和销毁机制。如果一个对象需要对另一个对象的引用,它应该在构造时传递对它的引用,或者在内部创建一个新的对象实例。但是当你使用单件的时候,你就明显地混淆了创建和拆卸的循环。一个相关的问题是很难控制单体的寿命。因此,许多包含通用单例实现的包还包括笨拙的对象生存期管理器等。有时我想知道这些是否仅仅是为了管理单件物品而存在。
基本上,如果您需要在许多地方使用一个对象,那么应该在栈中的最高公共点显式地创建它,然后通过引用传递给使用它的每个人。有时人们使用singleton是因为他们在将多个参数传递给新线程时遇到问题,但不要为此而掉以轻心,显式定义线程参数并以相同的方式将它们传递给新线程。您会发现您的程序流更干净,并且不会因为静态初始化依赖性或错误的拆卸而出现令人不快的意外。
我知道这是一条古老的线,但似乎没有人提到实际的模式,适合OP正在尝试做的事情。我认为他所描述的需要被称为调解人模式。SourceMaking是一个学习/引用此类信息的绝佳网站。当然,我会把人们介绍给软件模式。此外,一般来说,不接受任何设计模式必然是内在的好或坏的概念是一个好主意。他们都有自己的用处,只有学会何时何地使用才是诀窍。对我来说,那些声明从不使用单件物品的人不理解他们的用处。
单身肯定不错。它们有它们的用途,其中一些非常好。单例开发往往会被缺乏经验的开发人员过度使用,因为这通常是他们所了解的第一种设计模式,而且相当简单,所以他们不考虑其含义就把它扔得到处都是。
每次你想用单件衣服的时候,试着考虑一下你为什么要这样做,以及使用这种方式的好处和缺点。
单例确实有效地创建了一组全局可访问的"东西"(数据或方法),我认为大多数人都会同意使用太多的全局变量不是一个好主意。类和对象定向的整个要点是将事物分组到离散的区域,而不是将所有的东西都放到一个巨大的全球空间中。
我发现比起单件衣服我更喜欢的一个"模式"是从上面往下传递需要的东西。我在应用程序初始化阶段创建了它们一次,然后将它们传递给所有需要访问它们的对象。它模仿了单体模式的"单体创造"部分,但没有"全局"部分。
单例的全部要点是,它适用于只有1的对象。您提到了一组类的数据控制。也许可以考虑一下,实际上,有些情况下,应用程序可能希望创建两组数据控制类,因此在这方面强制使用单例可能并不完全正确。相反,如果您在app init上创建了这些数据类,并将它们传递下去,那么您只能创建一个集,因为这正是您当前应用程序所需的,但您仍有可能在某个时刻,如果您需要第二个集,您可以轻松地创建它们。此外,数据控制类是否真的可以从应用程序的任何地方访问全局。我认为不是,相反,它们应该只能从较低级别的数据访问层访问。
有些人推荐了这本书。我会说,是的,这是一本很好的书,但是首先尝试找到一本关于一般架构的书,先阅读2/3/N层设计、封装、抽象和这些原则。这将为您提供一个更坚实的基础,使您能够理解GOF所讨论的模式的适当用法。
[编辑:另一次singleton变量可能有用的是,当您希望某个对象有一个访问点,但实际上实现细节可能不止一个。调用者不需要知道,在covers下,他们对singleton对象的请求实际上是针对几个可用对象进行解析的,并返回一个对象。我在想一个线程池之类的东西,在哪里使用,嘿,给我一个线程,我需要1个,但我不在乎哪一个]
谷歌似乎深信单身是个坏主意。
这并不意味着谷歌做的每件事都是完美的,也不意味着他们的每一个观点都是任何争论的终结,但他们已经写下了这个单子探测器来根除这些争论。自己决定吧。
我觉得单身汉很好。
单身并不是"那么糟糕"。如果你有很多相关的单件产品,你可以使用一家工厂来替换/整合其中的一些,而不会失去你所关心的任何东西,那么你应该这样做。
至于书,嗯,有点经典。
不,它们不一定是坏的。
对于一本书,你需要从经典开始。