辛格尔顿:应该如何使用它

编辑:从另一个问题,我提供了一个答案,有很多关于单身人士的问题/答案的链接:关于单身人士的更多信息在这里:

所以我已经读了线程单身人士:好devise还是拐杖?
争论仍然激烈。

我将Singletons看作devise模式(好的和坏的)。

单身人士的问题不是模式,而是用户(对不起,每个人)。 每个人和他们的父亲认为他们可以正确地执行一个(而且从我做过的多次采访中,大多数人不能)。 也因为大家都认为他们可以实现一个正确的单身人士,他们滥用模式,并在不适当的情况下使用它(用单身人士replace全局variables!)。

所以需要回答的主要问题是:

  • 什么时候应该使用Singleton
  • 你如何正确实施一个单身人士

我对这篇文章的希望是,我们可以在一个地方收集(而不是必须谷歌和search多个网站)何时(以及如何)正确使用单一的权威来源。 同样适当的是一个反用例和常见的错误实现清单,解释了为什么他们不能工作,并为了良好的实现他们的弱点。


所以让球滚动:
我会握住我的手说,这是我使用,但可能有问题。
我喜欢“Scott Myers”在他的着作“Effective C ++”中处理这个主题,

好的情况下使用单身(不是很多):

  • logging框架
  • 线程回收池
/* * 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; } }; 

好。 让我们一起批评和其他实现。
🙂

你们都错了。 阅读问题。 回答:

使用单身人士,如果:

  • 如果你需要在系统中只有一个types的对象

在以下情况下不要使用Singleton:

  • 如果你想节省内存
  • 如果你想尝试新的东西
  • 如果你想炫耀你知道多less
  • 因为其他人都在这样做(请参阅维基百科的货运邪教程序员 )
  • 在用户界面小部件
  • 它应该是一个caching
  • 在string中
  • 在会议中
  • 我可以整天去

如何创build最好的单身人士:

  • 越小越好。 我是一个极简主义者
  • 确保它是线程安全的
  • 确保它从不为空
  • 确保它只创build一次
  • 懒惰或系统初始化? 达到你的要求
  • 有时操作系统或JVM为你创build单例(例如,在Java中,每个类定义都是单例)
  • 提供一个析构函数或以某种方式弄清楚如何处置资源
  • 使用一点记忆

单身人士给你在一个class级中结合两个不良特质的能力。 这在几乎每一个方面都是错误的。

一个单身人士给你:

  1. 全局访问一个对象,和
  2. 保证只能创build一个这种types的对象

第一个是直截了当的。 全球通常是不好的。 除非我们真的需要它,否则我们不应该让全局对象可用。

二号听起来可能是有道理的,但让我们来思考一下。 你最后一次意外地*创build了一个新的对象而不是引用一个现有的对象? 由于这是标记为C ++,让我们使用该语言的一个例子。 你经常不小心写信吗?

 std::ostream os; os << "hello world\n"; 

当你打算写

 std::cout << "hello world\n"; 

当然不是。 我们不需要防范这个错误,因为那种错误不会发生。 如果是这样,正确的回应是回家睡觉12-20小时,希望你感觉好点。

如果只需要一个对象,只需创build一个实例。 如果一个对象应该是全球可访问的,使其成为全球性的。 但是,这并不意味着不可能创build它的其他实例。

“只有一个实例是可能的”约束并不能真正保护我们免受可能的错误。 但它确实使我们的代码非常难以重构和维护。 因为后来我们经常发现我们确实需要多个实例。 我们有多个数据库,我们有多个configuration对象,我们需要几个logging器。 我们的unit testing可能希望能够在每个testing中创build和重新创build这些对象,以举一个常见的例子。

所以,当且仅当我们同时需要一个单例时,我们需要使用单例:如果我们需要全局访问(这是罕见的,因为全局通常是不鼓励的) 我们需要防止任何人创build一个以上的实例类(这听起来像一个devise问题)。 我能看到的唯一原因是如果创build两个实例会破坏我们的应用程序状态 – 可能是因为该类包含一些静态成员或类似的愚蠢。 在这种情况下,显而易见的答案就是解决这个问题。 它不应该依靠唯一的例子。

如果你需要全局访问一个对象,使其成为一个全局的,如std::cout 。 但是不要限制可以创build的实例的数量。

如果你绝对肯定需要将一个类的实例数量限制为一个,并且不可能安全地处理创build第二个实例,然后执行该操作。 但是,也不要让它成为全球可访问的。

如果你确实需要两个特征,那么1)把它做成单身,2)让我知道你需要什么,因为我很难想象这样的情况。

单身问题不是它们的实现。 这是他们混淆了两个不同的概念,这两者都不是显而易见的。

1)单身人士提供一个对象的全局访问机制。 尽pipe在没有明确定义的初始化顺序的语言中,它们可能会稍微多一些线程安全或稍微可靠一些,但这种用法仍然是一个全局variables的道德等价物。 这是一个全局variables,用一些尴尬的语法(foo :: get_instance()而不是g_foo)来表示,但是它具有完全相同的目的(在整个程序中可以访问单个对象)并且具有完全相同的缺点。

2)单例防止一个类的多个实例化。 IME很less,这种function应该被烘烤成一个类。 这通常是一个更有关联的事情; 很多被认为是一对一的东西实际上只是一个而已。 海事组织一个更合适的解决scheme是只创build一个实例 – 直到你意识到你需要多个实例。

有一个模式的事情: 不要概括 。 当他们有用,当他们失败时,他们有所有情况。

当您必须testing代码时,单例可能会很糟糕。 你通常坚持一个类的实例,并可以select在构造函数中打开一个门或重置状态的方法等等。

其他的问题是,单身实际上只不过是变相的全局variables 。 当你的程序有太多的全局共享状态时,情况往往会回落,我们都知道。

它可能会使依赖性跟踪更难。 当一切都取决于你的单身人士,它是很难改变它,分裂为两个等,你通常坚持下去。 这也妨碍了灵活性。 调查一些dependency injection框架,试图缓解这个问题。

单variables基本上让你的语言复杂的全局状态,否则会使复杂的全局variables变得困难或不可能。

Java特别使用单例作为全局variables的替代,因为所有的东西都必须包含在一个类中。 最接近全局variables的是公共静态variables,可以像使用import staticvariables一样使用它们

C ++没有全局variables,但调用全局类variables的构造函数的顺序是未定义的。 因此,一个单例允许你推迟创build一个全局variables,直到第一次需要这个variables为止。

像Python和Ruby这样的语言很less使用单例,因为你可以在模块中使用全局variables。

那么什么时候使用单身人士好? 使用全局variables的时候几乎是正确的。

Alexandrescu的现代C ++devise有一个线程安全的,可inheritance的通用单例。

对于我的2p值,我认为定义单身人士的生命周期是非常重要的(当它是绝对必要的时候)。 我通常不会让静态get()函数实例化任何东西,并将设置和销毁留给主应用程序的某个专用部分。 这有助于突出显示单身人士之间的依赖关系 – 但是,正如上面所强调的,如果可能,最好避免这种依赖。

  • 你如何正确实施一个单身人士

有一个我从未见过的问题,我在之前的工作中碰到过的。 我们拥有在DLL之间共享的C ++单例,并且确保类的单个实例的通常机制不起作用。 问题是每个DLL都有自己的一组静态variables,以及EXE。 如果你的get_instance函数是内联的或是静态库的一部分,那么每个DLL都会包含它自己的“singleton”副本。

解决scheme是确保单例代码只在一个DLL或EXE中定义,或者创build一个具有这些属性的单例pipe理器来分解实例。

第一个示例不是线程安全的 – 如果两个线程同时调用getInstance,则该静态将成为PITA。 某种forms的互斥体将有所帮助。

正如其他人已经指出的,单身人士的主要缺点包括无法扩展他们,并失去实例化多个实例的权力,例如出于testing目的。

单身人士的一些有用的方面:

  1. 懒惰或预先实例化
  2. 方便的需要设置和/或状态的对象

但是,您不必使用单例来获得这些好处。 你可以写一个正常的对象来完成工作,然后让人们通过一个工厂(一个单独的对象)访问它。 如果需要的话,工厂可能只担心实例化,重用等等。 另外,如果你编程的是一个接口而不是一个具体的类,工厂可以使用策略,即你可以切换进出各种接口的实现。

最后,一家工厂可以使用像Spring这样的dependency injection技术。

当你初始化和运行对象时,有很多代码在运行,单身是非常方便的。 例如,当你设置一个持久化对象的时候使用iBatis,它必须读取所有的configuration文件,parsing这些映射文件,确保它们全部正确,等等。

如果你每次都这样做,性能将大大降低。 使用它在一个单身人士,你采取了一击,然后所有后续的通话不必这样做。

单身人士真正的失败是他们打破inheritance。 你不能派生一个新的类来给你扩展的function,除非你有权访问单引用的代码。 所以,除了Singleton将使你的代码紧密耦合(可以通过策略模式(即dependency injection)来修复)之外,它也将阻止你从版本(共享库)closures代码段。

所以即使logging器或线程池的例子都是无效的,应该由策略取代。

大多数人使用单身时,他们正在试图使自己感觉良好,使用全局variables。 有合理的用途,但大多数时候,当人们使用它们时,只能有一个实例的事实与其全球可访问的事实相比只是一个微不足道的事实。

因为单例只允许创build一个实例,所以它可以有效地控制实例复制。 例如,你不需要查询的多个实例 – 比如一个莫尔斯(morse)查找映射,因此将它包装在一个单例类中就是apt。 仅仅因为你有一个类的实例,并不意味着你也限制了这个实例的引用数量。 您可以将调用(以避免线程问题)排队到实例并进行必要的更改。 是的,单身人士的一般forms是全球公共的,你当然可以修改devise来创build一个更受访问限制的单身人士。 我以前没有厌倦过,但我确定知道这是可能的。 对于所有那些评论说单身模式是完全邪恶的人,你应该知道这一点:是的,如果你不恰当地使用它,或者在它内部使用有效的function和可预见的行为,那就是邪恶的:不要泛化。

但是当我需要像Singleton这样的东西的时候,我最终会用一个Schwarz Counter来实例化它。

我使用单身人士作为面试。

当我要求开发人员命名一些devise模式时,如果他们的名字都是Singleton,那么他们不会被雇用。

反用法:

过度单例使用的一个主要问题是该模式阻止了其他实现的简单扩展和交换。 在使用单例的地方,类名是硬编码的。

我认为这是C# 最强大的版本

 using System; using System.Collections; using System.Threading; namespace DoFactory.GangOfFour.Singleton.RealWorld { // MainApp test application class MainApp { static void Main() { LoadBalancer b1 = LoadBalancer.GetLoadBalancer(); LoadBalancer b2 = LoadBalancer.GetLoadBalancer(); LoadBalancer b3 = LoadBalancer.GetLoadBalancer(); LoadBalancer b4 = LoadBalancer.GetLoadBalancer(); // Same instance? if (b1 == b2 && b2 == b3 && b3 == b4) { Console.WriteLine("Same instance\n"); } // All are the same instance -- use b1 arbitrarily // Load balance 15 server requests for (int i = 0; i < 15; i++) { Console.WriteLine(b1.Server); } // Wait for user Console.Read(); } } // "Singleton" class LoadBalancer { private static LoadBalancer instance; private ArrayList servers = new ArrayList(); private Random random = new Random(); // Lock synchronization object private static object syncLock = new object(); // Constructor (protected) protected LoadBalancer() { // List of available servers servers.Add("ServerI"); servers.Add("ServerII"); servers.Add("ServerIII"); servers.Add("ServerIV"); servers.Add("ServerV"); } public static LoadBalancer GetLoadBalancer() { // Support multithreaded applications through // 'Double checked locking' pattern which (once // the instance exists) avoids locking each // time the method is invoked if (instance == null) { lock (syncLock) { if (instance == null) { instance = new LoadBalancer(); } } } return instance; } // Simple, but effective random load balancer public string Server { get { int r = random.Next(servers.Count); return servers[r].ToString(); } } } } 

这是.NET优化的版本

 using System; using System.Collections; namespace DoFactory.GangOfFour.Singleton.NETOptimized { // MainApp test application class MainApp { static void Main() { LoadBalancer b1 = LoadBalancer.GetLoadBalancer(); LoadBalancer b2 = LoadBalancer.GetLoadBalancer(); LoadBalancer b3 = LoadBalancer.GetLoadBalancer(); LoadBalancer b4 = LoadBalancer.GetLoadBalancer(); // Confirm these are the same instance if (b1 == b2 && b2 == b3 && b3 == b4) { Console.WriteLine("Same instance\n"); } // All are the same instance -- use b1 arbitrarily // Load balance 15 requests for a server for (int i = 0; i < 15; i++) { Console.WriteLine(b1.Server); } // Wait for user Console.Read(); } } // Singleton sealed class LoadBalancer { // Static members are lazily initialized. // .NET guarantees thread safety for static initialization private static readonly LoadBalancer instance = new LoadBalancer(); private ArrayList servers = new ArrayList(); private Random random = new Random(); // Note: constructor is private. private LoadBalancer() { // List of available servers servers.Add("ServerI"); servers.Add("ServerII"); servers.Add("ServerIII"); servers.Add("ServerIV"); servers.Add("ServerV"); } public static LoadBalancer GetLoadBalancer() { return instance; } // Simple, but effective load balancer public string Server { get { int r = random.Next(servers.Count); return servers[r].ToString(); } } } } 

你可以在dotfactory.comfind这个模式。

Meyers的单身模式在大多数情况下运行得很好,在某些情况下,它并不一定会付出更好的代价。 只要构造函数永远不会抛出,并且在单例之间没有依赖关系。

单身人士是全球可访问对象 (GAO从现在开始)的实现,尽pipe不是所有的GAO都是单身人士。

logging器本身不应该是单身人士,但logging的手段理想情况下应该是全球可访问的,以解耦日志消息从哪里生成或从何处logging。

懒惰加载/懒惰评估是一个不同的概念,单身人士通常也实现这一点。 它带来了很多自己的问题,特别是线程安全性和问题,如果它以例外的方式失败,那么当时看起来不错的想法毕竟不是那么好。 (有点像string中的COW实现)。

考虑到这一点,可以像这样初始化GOA:

 namespace { T1 * pt1 = NULL; T2 * pt2 = NULL; T3 * pt3 = NULL; T4 * pt4 = NULL; } int main( int argc, char* argv[]) { T1 t1(args1); T2 t2(args2); T3 t3(args3); T4 t4(args4); pt1 = &t1; pt2 = &t2; pt3 = &t3; pt4 = &t4; dostuff(); } T1& getT1() { return *pt1; } T2& getT2() { return *pt2; } T3& getT3() { return *pt3; } T4& getT4() { return *pt4; } 

不需要像这样粗暴地完成,显然在一个包含对象的加载库中,你可能需要其他一些机制来pipe理它们的生命周期。 (把它们放在你加载库的时候得到的对象中)。

至于什么时候用单身? 我用它们做了两件事情 – 一个单例表,表明了用dlopen加载了哪些库 – 一个logging器可以订阅的消息处理程序,你可以发送消息给它。 信号处理程序专门需要。

我仍然不明白为什么一个单身人士必须是全球性的。

我打算生成一个单例,在该类中隐藏一个数据库作为一个私有的常量静态variables,并创build使用该数据库的类函数,而不会将该数据库暴露给用户。

我不明白为什么这个function会不好。

I find them useful when I have a class that encapsulates a lot of memory. For example in a recent game I've been working on I have an influence map class that contains a collection of very large arrays of contiguous memory. I want that all allocated at startup, all freed at shutdown and I definitely want only one copy of it. I also have to access it from many places. I find the singleton pattern to be very useful in this case.

I'm sure there are other solutions but I find this one very useful and easy to implement.

In desktop apps (I know, only us dinosaurs write these anymore!) they are essential for getting relatively unchanging global application settings – the user language, path to help files, user preferences etc which would otherwise have to propogate into every class and every dialog.

Edit – of course these should be read-only !

Another implementation

 class Singleton { public: static Singleton& Instance() { // lazy initialize if (instance_ == NULL) instance_ = new Singleton(); return *instance_; } private: Singleton() {}; static Singleton *instance_; };