编辑:从另一个问题我提供了一个答案,链接到很多关于单身人士的问题/答案:有关单身人士的更多信息:
所以我读过Singletons的帖子:好的设计还是拐杖?
争论仍然激烈.
我认为单身人士是一种设计模式(好的和坏的).
Singleton的问题不是模式而是用户(对不起所有人).每个人和他们的父亲都认为他们可以正确地实施一个(而且从我做过的许多采访中,大多数人都做不到).此外,因为每个人都认为他们可以实现正确的Singleton,他们滥用模式并在不合适的情况下使用它(用Singletons替换全局变量!).
所以需要回答的主要问题是:
什么时候应该使用Singleton
如何正确实现Singleton
我对这篇文章的希望是,我们可以在一个地方收集(而不是谷歌和搜索多个网站)一个权威的来源,了解何时(以及如何)正确使用单身人士.同样合适的还有一份反用法和常见的不良实施清单,解释了为什么他们无法工作以及为了实现他们的弱点.
所以让球滚动:
我会举起手来说这是我用的,但可能有问题.
我喜欢"Scott Myers"在他的书"Effective C++"中处理这个主题
使用单身人士的好情况(不是很多):
记录框架
线程回收池
/* * C++ Singleton * Limitation: Single Threaded Design * See: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf * For problems associated with locking in multi threaded applications * * Limitation: * If you use this Singleton (A) within a destructor of another Singleton (B) * This Singleton (A) must be fully constructed before the constructor of (B) * is called. */ class MySingleton { private: // Private Constructor MySingleton(); // Stop the compiler generating methods of copy the object MySingleton(MySingleton const& copy); // Not Implemented MySingleton& operator=(MySingleton const& copy); // Not Implemented public: static MySingleton& getInstance() { // The only instance // Guaranteed to be lazy initialized // Guaranteed that it will be destroyed correctly static MySingleton instance; return instance; } };
好.让我们一起批评和其他实施.
:-)
你们所有人都错了.阅读问题.回答:
在下列情况下使用单身人士
您需要在系统中拥有一个且只有一个类型的对象
如果出现以下情况,请勿使用Singleton:
你想节省内存
你想尝试新的东西
你想炫耀你知道多少
因为其他人都在这样做(参见维基百科的货运程序员)
在用户界面小部件中
它应该是一个缓存
在字符串中
在Sessions中
我可以整天走
如何创建最好的单身人士:
越小越好.我是一个极简主义者
确保它是线程安全的
确保它永远不会为空
确保它只创建一次
懒惰还是系统初始化?符合您的要求
有时OS或JVM会为您创建单例(例如,在Java中,每个类定义都是单例)
提供析构函数或以某种方式弄清楚如何处置资源
使用少量记忆
单身人士可以让你在一个班级中结合两个不良特质.几乎在各方面都是错的.
单身人士给你:
全局访问对象,和
保证永远不会创建多于一个此类型的对象
第一是很简单.全局通常都很糟糕.除非我们确实需要,否则我们不应该让对象全局可访问.
第二个听起来似乎有道理,但让我们考虑一下.你最后一次**意外*创建了一个新对象而不是引用一个现有对象是什么时候?由于这是标记的C++,让我们使用该语言的一个例子.你经常不小心写
std::ostream os; os << "hello world\n";
当你打算写
std::cout << "hello world\n";
当然不是.我们不需要针对此错误的保护,因为这种错误不会发生.如果确实如此,正确的反应是回家睡12-20个小时,希望你感觉好些.
如果只需要一个对象,只需创建一个实例.如果一个对象应该可以全局访问,那么将其设为全局对象.但这并不意味着创建它的其他实例应该是不可能的.
"只有一个实例是可能的"约束并不能真正保护我们免受可能的错误.但它确实使我们的代码很难重构和维护.因为很多时候,我们发现以后,我们确实需要一个以上的实例.我们确实有多个数据库,我们确实有多个配置对象,我们确实需要几个记录器.我们的单元测试可能希望能够在每次测试时创建和重新创建这些对象,以便采用一个常见的示例.
因此,一个单应使用当且仅当,我们需要这两个性状它提供了:如果我们需要全球性的访问(这是罕见的,因为全局一般气馁)和我们需要防止任何人曾经创造的多个实例class(听起来像是一个设计问题).我能看到的唯一原因是,如果创建两个实例会破坏我们的应用程序状态 - 可能是因为该类包含许多静态成员或类似的愚蠢.在这种情况下,明显的答案是修复该课程.它不应该依赖于唯一的实例.
如果您需要对对象进行全局访问,请将其设置为全局对象std::cout
.但是不要限制可以创建的实例数量.
如果你绝对需要将类的实例数限制为一个,并且无法安全地处理创建第二个实例,那么就强制执行.但是也不要让它在全球范围内可访问.
如果你确实需要两个特征,那么1)使它成为单身,2)让我知道你需要什么,因为我很难想象这样的情况.
单身人士的问题不在于他们的实施.它们将两个不同的概念混为一谈,这两个概念都不是明显可取的.
1)单身人士为对象提供全局访问机制.虽然在没有明确定义的初始化顺序的语言中它们可能稍微更线程安全或稍微更可靠,但这种用法仍然是全局变量的道德等价物.它是一个全局变量,装在一些笨拙的语法中(foo :: get_instance()而不是g_foo,比如说),但它服务于完全相同的目的(在整个程序中可访问的单个对象)并具有完全相同的缺点.
2)单身人士阻止一个类的多个实例化.很少见,IME,这种功能应该融入一个类.这通常是一个更具背景性的事情; 许多被认为是独一无二的东西真的只是恰好只有一个.IMO更合适的解决方案是只创建一个实例 - 直到您意识到需要多个实例.
模式有一点:不要概括.当它们有用时,以及当它们失败时,它们都有所有的情况.
当你必须测试代码时,单身人士会很讨厌.您通常会遇到类的一个实例,并且可以选择在构造函数中打开一个门,还是在重置状态等方法之间进行选择.
其他问题是,单身实际上只不过是伪装的全局变量.当你的程序中有太多的全局共享状态时,事情往往会回归,我们都知道.
它可能使依赖性跟踪更难.当一切都取决于你的单身人士时,更难改变它,分成两个等等.你通常会坚持下去.这也妨碍了灵活性.调查一些依赖注入框架以尝试缓解此问题.
单身人士基本上会让你在语言中拥有复杂的全局状态,否则很难或不可能拥有复杂的全局变量.
Java特别使用单例作为全局变量的替代,因为所有内容都必须包含在类中.它与全局变量最接近的是公共静态变量,它们可以像使用全局变量一样使用import static
C++确实有全局变量,但是未定义调用全局类变量的构造函数的顺序.因此,单例允许您推迟创建全局变量,直到第一次需要该变量.
Python和Ruby等语言很少使用单例,因为您可以在模块中使用全局变量.
那么什么时候使用单身人物好/坏?几乎就是使用全局变量时好/坏的时候.
Alexandrescu的现代C++设计具有线程安全,可继承的通用单例.
对于我的2p值,我认为为你的单身人士确定生命周期是很重要的(当它绝对有必要使用它们时).我通常不会让静态get()
函数实例化任何东西,并将设置和销毁留给主应用程序的某个专用部分.这有助于突出单身人士之间的依赖关系 - 但正如上面所强调的那样,如果可能的话,最好避免使用它们.
如何正确实现Singleton
有一个我从未见过的问题,这是我以前的工作遇到的问题.我们有在DLL之间共享的C++单例,并且确保单个类实例的常用机制不起作用.问题是每个DLL都有自己的一组静态变量以及EXE.如果你的get_instance函数是内联函数或者是静态库的一部分,那么每个DLL都将使用它自己的"singleton"副本.
解决方案是确保单例代码仅在一个DLL或EXE中定义,或者创建具有这些属性的单例管理器以包装实例.
第一个例子不是线程安全的 - 如果两个线程同时调用getInstance,那个静态就是PITA.某种形式的互斥体会有所帮助.
正如其他人指出的那样,单例的主要缺点包括无法扩展它们,并失去了实例化多个实例(例如出于测试目的)的能力。
单例的一些有用方面:
惰性或前期实例化
方便用于需要设置和/或状态的对象
但是,您不必使用单例即可获得这些好处。您可以编写一个可以完成工作的普通对象,然后让人们通过工厂(一个单独的对象)访问它。如果需要,工厂可以担心仅实例化一个,然后重新使用它等等。同样,如果您编程到接口而不是具体的类,则工厂可以使用策略,即可以切入和切出接口的各种实现。
最后,工厂适合采用Spring等依赖项注入技术。