Singleton:应如何使用
编辑:
从另一个问题中,我提供了一个答案,该答案具有许多有关单身人士的问题/答案的链接:有关单身人士的更多信息,请点击此处:
因此,我读了Singletons主题:好的设计还是拐杖?
而且争论仍然很激烈。
我将单例视为一种设计模式(好的和坏的)。
Singleton的问题不是模式,而是用户(对不起每个人)。每个人和他们的父亲都认为他们可以正确实施一个方案(从我进行的许多访谈中,大多数人都做不到)。同样因为每个人都认为他们可以实现正确的Singleton,所以他们滥用Pattern并将其用于不合适的情况(用Singletons代替全局变量!)。
因此,需要回答的主要问题是:
- 什么时候应该使用Singleton
- 我们如何正确实现Singleton
我对本文的希望是,我们可以在一个地方(而不是谷歌搜索多个网站)一起收集何时(然后如何)正确使用Singleton的权威来源。同样合适的是反使用和常见的不良实现的列表,这些列表解释了为什么它们无法正常工作,而对于好的实现则有其缺点。
因此,滚滚滚滚的球:
我会举起手,说这是我用的,但可能有问题。
我喜欢他的"有效C ++"一书中对"斯科特·迈尔斯(Scott Myers)"的处理
Good Situations to use Singletons (not many): Logging frameworks Thread recycling pools
/* * 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; } };
好的。让我们一起进行一些批评和其他实现。
:-)
解决方案
回答
如果两个线程同时调用getInstance,则第一个示例不是线程安全的,该静态对象将是PITA。某种形式的互斥锁会有所帮助。
回答
单例基本上使我们可以使用多种语言来拥有复杂的全局状态,否则将很难或者不可能拥有复杂的全局变量。
尤其是Java,因为所有内容都必须包含在一个类中,所以它使用单例代替全局变量。与全局变量最接近的是公共静态变量,可以将其用作带有" import static"的全局变量。
C ++确实具有全局变量,但是全局类变量的构造函数的调用顺序是不确定的。这样,单例可让我们将全局变量的创建推迟到第一次需要该变量时进行。
诸如Python和Ruby之类的语言很少使用单例,因为我们可以在模块内使用全局变量。
那么什么时候使用单例是好是坏?确切地说,何时使用全局变量是好是坏。
回答
反使用:
单例使用过多的一个主要问题是该模式阻止了易于扩展和交换其他实现。无论在何处使用单例,都对类别名称进行硬编码。
回答
模式的一件事:不要一概而论。在所有情况下,当它们有用时都会失败。
当我们必须测试代码时,Singleton可能会令人讨厌。通常,我们只能使用该类的一个实例,可以选择在构造函数中打开一扇门,也可以选择某种方法来重置状态,依此类推。
另一个问题是,单例实际上不过是变相的全局变量而已。当程序具有太多的全局共享状态时,情况往往会倒退,我们都知道。
这可能会使依赖项跟踪更加困难。当一切都取决于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.com上找到此模式。
回答
单例的问题不是它们的实现。正是它们将两个不同的概念混为一谈,这两个概念显然都不可取。
1)单例为对象提供全局访问机制。尽管在没有明确定义的初始化顺序的语言中,它们在线程安全性上可能更高一些,在某种程度上也更可靠,但这种用法仍然在道德上等同于全局变量。这是一个用一些笨拙的语法(例如foo :: get_instance()而不是g_foo)修饰的全局变量,但是它具有完全相同的目的(可以在整个程序中访问单个对象),并且具有完全相同的缺点。
2)单例阻止一个类的多个实例化。 IME很少将这种功能归为一类。通常,这是上下文相关的事情;许多被认为是唯一的事情实际上只是发生在唯一的事情上。 IMO一个更合适的解决方案是仅创建一个实例-直到我们意识到需要多个实例为止。
回答
当初始化和对象运行大量代码时,单例很方便。例如,当我们使用iBatis设置持久性对象时,它必须读取所有配置,解析地图,确保其所有正确信息,等等。
如果我们每次都这样做,则性能将大大降低。在单例中使用它时,我们只需击中一次击键,然后随后的所有调用都不必这样做。
回答
Singletons真正的失败之处在于它们破坏了继承。除非我们有权访问引用Singleton的代码,否则不能派生新类来提供扩展功能。因此,除了Singleton可以使代码紧密耦合(可以通过策略模式... aka依赖注入进行固定)之外,它还可以防止我们关闭版本(共享库)中的代码部分。
因此,即使记录器或者线程池的示例也是无效的,应将其替换为"策略"。
回答
在台式机应用程序中(我知道,只有我们的恐龙会再写这些!),它们对于获得相对不变的全局应用程序设置至关重要,这些设置包括用户语言,帮助文件的路径,用户首选项等,否则它们必须传播到每个类和每个对话框中。
当然,这些内容应该是只读的!
回答
我使用Singletons作为面试测试。
当我要求开发人员命名某些设计模式时,如果他们只能命名为Singleton,则不会雇用他们。
回答
但是,当我需要诸如Singleton之类的东西时,我常常最终使用Schwarz Counter实例化它。
回答
Alexandrescu编写的Modern C ++ Design具有线程安全的,可继承的通用单例。
对于我的2p值,我认为为单身人士定义生存期非常重要(当绝对有必要使用它们时)。我通常不让静态的get()函数实例化任何东西,而将设置和销毁工作留给主应用程序的某些专用部分。这有助于突出显示单例之间的依赖性,但是,如上所述,最好尽可能避免它们。
回答
你们所有人都是错的。
阅读问题。
回答:
在以下情况下使用单例:
- 我们在系统中仅需要一个类型的对象
在以下情况下,请勿使用Singleton:
- 我们想节省内存
- 你想尝试一些新东西
- 你想炫耀你知道多少
- 因为其他所有人都在这样做(请参阅Wikipedia中的cult程序员)
- 在用户界面小部件中
- 应该是缓存
- 在字符串中
- 会话中
- 我可以整天去
如何创建最佳单身人士:
- 越小越好。我是一个极简主义者
- 确保它是线程安全的
- 确保它永远不会为空
- 确保仅创建一次
- 延迟还是系统初始化?根据要求
- 有时OS或者JVM会为我们创建单例(例如,在Java中,每个类定义都是一个单例)
- 提供一个析构函数或者以某种方式弄清楚如何处置资源
- 占用很少的内存
回答
单身人士使我们能够在一个班级中结合两个不良特征。在几乎所有方面这都是错误的。
单例为我们提供:
- 全局访问对象,以及
- 保证最多只能创建一个这种类型的对象
第一很简单。全球人通常是坏的。除非我们确实需要,否则绝不应该使对象在全局范围内可访问。
第二个听起来似乎很有意义,但让我们考虑一下。我们上次"偶然地" *创建新对象而不是引用现有对象是什么时候?由于此标记为C ++,因此让我们使用该语言的示例。你经常不小心写吗
std::ostream os; os << "hello world\n";
当你打算写的时候
std::cout << "hello world\n";
当然不是。我们不需要针对此错误的保护措施,因为这种错误根本不会发生。如果是这样,正确的反应是回家睡觉12至20个小时,希望我们感觉好些。
如果只需要一个对象,则只需创建一个实例。如果一个对象可以全局访问,则使其成为全局对象。但这并不意味着就不可能创建它的其他实例。
"只有一个实例是可能的"约束并不能真正保护我们免受可能的错误侵害。但这确实使我们的代码很难重构和维护。因为很多时候我们以后才发现我们确实需要多个实例。我们确实有多个数据库,我们有多个配置对象,我们想要多个记录器。举一个常见的例子,我们的单元测试可能希望能够在每个测试中创建和重新创建这些对象。
因此,当且仅当我们需要它提供的两个特征时,才应使用单例:如果我们需要全局访问(这种情况很少见,因为通常不鼓励使用全局变量),并且我们需要防止任何人创建一个以上的a实例。类(在我看来,这是一个设计问题)。我能看到的唯一原因是,创建两个实例是否会破坏我们的应用程序状态,可能是因为该类包含许多静态成员或者类似的愚蠢行为。在这种情况下,显而易见的答案是修复该类。它不应该依赖于唯一的实例。
如果我们需要全局访问某个对象,请将其设为全局对象,例如std :: cout
。但不要限制可以创建的实例数量。
如果绝对需要,肯定需要将一个类的实例数限制为一个,并且无法安全地创建第二个实例,然后执行。但是也不要使其在全球范围内都可访问。
如果我们确实需要两个特征,那么1)使其成为一个单例,2)让我知道需求,因为我很难想象这种情况。
回答
How do you implement a Singleton correctly
我从来没有提到过一个问题,这是我在上一份工作中遇到的一个问题。我们有在DLL之间共享的C ++单例,并且确保类的单个实例不起作用的通常机制。问题在于,每个DLL都有自己的一组静态变量以及EXE。如果get_instance函数是内联函数或者是静态库的一部分,则每个DLL都将带有其自己的"单例"副本。
解决方案是确保仅在一个DLL或者EXE中定义单例代码,或者使用这些属性创建单例管理器以打包实例。
回答
大多数人在尝试使自己对使用全局变量感到满意时会使用单例。有合法的用途,但是在大多数情况下,人们使用它们时,只能存在一个实例这一事实与可全局访问的事实相比,只是一个琐碎的事实。
回答
因为单例只允许创建一个实例,所以它有效地控制了实例的复制。例如,我们不需要一个莫尔斯(Morse)查找地图的多个查找实例,因此将其包装在Singleton类中是很合适的。而且,仅因为我们具有该类的单个实例,并不意味着我们对该实例的引用数量也受到限制。我们可以将对实例的调用排队(以避免线程问题)并影响必要的更改。是的,单例的一般形式是全球公开的形式,我们当然可以修改设计以创建更多受访问限制的单例。我以前没有为此感到疲倦,但我肯定知道有可能。
对于所有评论说单例模式完全是邪恶的人,我们应该知道这一点:是的,如果我们未正确使用它或者在其中使用它会限制有效的功能和可预测的行为,那么这是邪恶的:不要概括。
回答
正如其他人指出的那样,单例的主要缺点包括无法扩展它们,并失去了实例化多个实例(例如,实例)的能力。用于测试目的。
单例的一些有用方面:
- 惰性或者前期实例化
- 方便用于需要设置和/或者状态的对象
但是,我们不必使用单例即可获得这些好处。我们可以编写一个可以完成工作的普通对象,然后让人们通过工厂(一个单独的对象)对其进行访问。如果需要,工厂可以担心仅实例化一个,然后重新使用它等等。另外,如果我们是针对接口而不是具体的类进行编程,则工厂可以使用策略,即可以切换界面的各种实现。
最后,工厂适合于使用依赖注入技术,例如Spring等。