it-swarm.cn

Singleton:如何使用它

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

所以我已阅读帖子 单身人士:好的设计还是拐杖?
并且争论仍然激烈。

我认为单身人士是一种设计模式(好的和坏的)。

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;
        }
};

好。让我们一起批评和其他实施。
:-)

288
Martin York

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

在下列情况下使用单身人士

  • 您需要在系统中拥有一个且只有一个类型的对象

如果出现以下情况,请勿使用Singleton:

  • 你想节省内存
  • 你想尝试新的东西
  • 你想炫耀你知道多少
  • 因为其他人都在这样做(参见 货运邪教程序员 在维基百科中)
  • 在用户界面小部件中
  • 它应该是一个缓存
  • 在字符串中
  • 在Sessions中
  • 我可以整天走

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

  • 越小越好。我是一个极简主义者
  • 确保它是线程安全的
  • 确保它永远不会为空
  • 确保它只创建一次
  • 懒惰还是系统初始化?符合您的要求
  • 有时OS或JVM会为您创建单例(例如,在Java中,每个类定义都是单例)
  • 提供析构函数或以某种方式弄清楚如何处置资源
  • 使用少量记忆
165
Javaxpert

单身人士可以让你在一个班级中结合两个不良特质。几乎在各方面都是错的。

单身人士给你:

  1. 全局访问对象,和
  2. 保证不能创建多于一个此类型的对象

第一是很简单。全局通常都很糟糕。我们永远不应该让对象全局可访问,除非我们 真的 需要它。

第二个听起来似乎有道理,但让我们考虑一下。你最后一次**意外*创建了一个新对象而不是引用一个现有对象是什么时候?由于这是标记的C++,让我们使用该语言的一个例子。你经常不小心写

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

当你打算写

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

当然不是。我们不需要针对此错误的保护,因为这种错误不会发生。如果确实如此,正确的反应是回家睡12-20个小时,希望你感觉好些。

如果只需要一个对象,只需创建一个实例。如果一个对象应该可以全局访问,请将其设置为全局对象。但这并不意味着创建它的其他实例应该是不可能的。

“只有一个实例是可能的”约束并不能真正保护我们免受可能的错误。但它 确实 使我们的代码很难重构和维护。因为我们经常发现 后来 我们确实需要多个实例。我们 do 有多个数据库,我们 do 有多个配置对象,我们确实需要几个记录器。我们的单元测试可能希望能够在每次测试时创建和重新创建这些对象,以便采用一个常见的示例。

因此,当且仅当我们需要 both 它提供的特征时才应使用单例:如果我们 需要 全局访问(这是罕见的,因为通常不鼓励全局变量)we need 防止任何人 永远 创建一个以上类的实例(这听起来像是一个设计问题)。我能看到的唯一原因是,如果创建两个实例会破坏我们的应用程序状态 - 可能是因为该类包含许多静态成员或类似的愚蠢。在这种情况下,明显的答案是修复该类。它不应该依赖于唯一的实例。

如果您需要对对象进行全局访问,请将其设置为全局,如std::cout。但是不要限制可以创建的实例数量。

如果你绝对需要将类的实例数限制为一个,并且无法安全地处理创建第二个实例,那么就强制执行。但是也不要让它在全球范围内可访问。

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

70
jalf

单身人士的问题不在于他们的实施。它们将两个不同的概念混为一谈,这两个概念都不是明显可取的。

1)单身人士为对象提供全局访问机制。虽然在没有明确定义的初始化顺序的语言中它们可能稍微更线程安全或稍微更可靠,但这种用法仍然是全局变量的道德等价物。它是一个全局变量,装在一些笨拙的语法中(foo :: get_instance()而不是g_foo,比如说),但它服务于完全相同的目的(在整个程序中可访问的单个对象)并具有完全相同的缺点。

2)单身人士阻止一个类的多个实例化。很少见,IME,这种功能应该融入一个类。这通常是一个更具背景性的事情;许多被认为是独一无二的东西真的只是恰好只有一个。 IMO更合适的解决方案是只创建一个实例 - 直到您意识到需要多个实例。

35
DrPizza

模式有一点: 不要概括 。当它们有用时,以及当它们失败时,它们都有所有的情况。

当你必须 测试 代码时,单身人士会很讨厌。您通常会遇到类的一个实例,并且可以选择在构造函数中打开一个门,还是在重置状态等方法之间进行选择。

另一个问题是,Singleton实际上只不过是一个 全局变量 伪装。当你的程序中有太多的全局共享状态时,事情往往会回归,我们都知道。

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

26
Paweł Hajdan

单身人士基本上会让你在语言中拥有复杂的全局状态,否则很难或不可能拥有复杂的全局变量。

Java特别使用单例作为全局变量的替代,因为所有内容都必须包含在类中。它与全局变量最接近的是公共静态变量,可以像使用import static一样使用它们。

C++确实有全局变量,但是未定义调用全局类变量的构造函数的顺序。因此,单例允许您推迟创建全局变量,直到第一次需要该变量。

Python和Ruby等语言很少使用单例,因为您可以在模块中使用全局变量。

那么什么时候使用单身人物好/坏?几乎就是使用全局变量时好/坏的时候。

12
Eli Courtwright
  • 如何正确实现Singleton

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

解决方案是确保单例代码仅在一个DLL或EXE中定义,或者创建具有这些属性的单例管理器以包装实例。

6
Mark Ransom

现代C++设计 由Alexandrescu提供了一个线程安全的,可继承的通用单例。

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

6
tenpn

第一个例子不是线程安全的 - 如果两个线程同时调用getInstance,那个静态就是PITA。某种形式的互斥体会有所帮助。

5
Rob

正如其他人所指出的那样,单例的主要缺点包括无法扩展它们,以及失去实例化多个实例的能力,例如:用于测试目的。

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

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

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

最后,工厂适用于依赖注入技术,如Spring等。

4
lexh

因为单例只允许创建一个实例,所以它有效地控制了实例复制。例如,您不需要多个查找实例 - 例如莫尔斯查找映射,因此将其包装在单例类中是apt。只是因为你有一个类的实例并不意味着你也受限于对该实例的引用数量。您可以将调用(以避免线程问题)排队到实例并进行必要的更改。是的,单身人士的一般形式是全球公共形式,您当然可以修改设计以创建更多访问受限制的单身人士。我以前没有累过这个,但我确定知道这是可能的。对于那些评论说单身模式完全是邪恶的人,你应该知道这一点:是的,如果你没有正确地使用它,或者在有限的功能和可预测的行为范围内它是邪恶的:不要通用。

3
gogole

当你初始化和反对时运行大量代码时,单身人士会很方便。例如,当您在设置持久性对象时使用iBatis时,它必须读取所有配置,解析映射,确保其全部正确等等。然后才能获取代码。

如果你每次都这样做,性能会大大降低。在单例中使用它,您可以使用该命中一次,然后所有后续调用都不必执行此操作。

3
Brian

大多数人在试图让自己对使用全局变量感觉良好时会使用单身人士。有合法的用途,但大多数时候人们使用它们,事实上只有一个实例只是一个微不足道的事实,相比之下它是全球可访问的。

3
Brad Barker

单身人士的真正垮台是他们打破了继承权。除非您可以访问引用Singleton的代码,否则无法派生新类来为您提供扩展功能。因此,除了Singleton将使您的代码紧密耦合(可通过策略模式修复...也称为依赖注入)之外,它还将阻止您从修订(共享库)关闭代码部分。

因此,即使记录器或线程池的示例也是无效的,应该由Strategies替换。

3
ZebZiggle

但是当我需要像Singleton这样的东西时,我经常最终使用 Schwarz Counter 来实例化它。

2
Matt Cruikshank

我使用Singletons作为面试测试。

当我要求开发人员命名一些设计模式时,如果他们只能命名为Singleton,那么他们就不会被雇用。

1
Matt Cruikshank

下面是实现线程安全单例模式的更好方法,在析构函数本身中释放内存。但我认为析构函数应该是可选的,因为单例实例将在程序终止时自动销毁:

#include<iostream>
#include<mutex>

using namespace std;
std::mutex mtx;

class MySingleton{
private:
    static MySingleton * singletonInstance;
    MySingleton();
    ~MySingleton();
public:
    static MySingleton* GetInstance();
    MySingleton(const MySingleton&) = delete;
    const MySingleton& operator=(const MySingleton&) = delete;
    MySingleton(MySingleton&& other) noexcept = delete;
    MySingleton& operator=(MySingleton&& other) noexcept = delete;
};

MySingleton* MySingleton::singletonInstance = nullptr;
MySingleton::MySingleton(){ };
MySingleton::~MySingleton(){
    delete singletonInstance;
};

MySingleton* MySingleton::GetInstance(){
    if (singletonInstance == NULL){
        std::lock_guard<std::mutex> lock(mtx);
        if (singletonInstance == NULL)
            singletonInstance = new MySingleton();
    }
    return singletonInstance;
}

关于我们需要使用单例类的情况可以是 - 如果我们想要在整个程序执行期间维护实例的状态如果我们参与写入应用程序的执行日志,其中只有一个文件实例需要使用....等等。如果任何人可以在我的上述代码中建议优化,那将是值得注意的。

1
A. Gupta

迈耶斯单身人士模式在大多数情况下运作良好,并且在它做的时候,并不一定要花更多的钱去寻找更好的东西。只要构造函数永远不会抛出并且单例之间没有依赖关系。

单例是 全局可访问对象的实现 (从现在起GAO),尽管并非所有GAO都是单例。

记录器本身不应该是单例,但记录方法理想情况下应该是全局可访问的,以便从生成日志消息的位置和记录的位置进行分离。

延迟加载/延迟评估是一个不同的概念,单例通常也实现它。它带来了很多自己的问题,特别是线程安全和问题,如果它失败了,那么当时看起来好主意的事实证明并不是那么好。 (有点像字符串中的COW实现)。

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

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;
}

它不需要粗暴地完成,显然在包含对象的加载库中,您可能需要一些其他机制来管理它们的生命周期。 (将它们放在加载库时获得的对象中)。

至于什么时候我使用单身人士?我将它们用于2件事 - 一个表示已用dlopen加载的库的单例表 - 记录器可以订阅的消息处理程序以及可以发送消息的消息处理程序。特别适用于信号处理程序。

0
CashCow

如果你是创建单身人士并且使用它的人,不要把它作为单身人士(它没有意义,因为你可以控制对象的单一性而不使它成为单身人士)但是当你是一个开发者时它是有意义的库并且您只想为用户提供一个对象(在这种情况下,您是创建单例的人,但您不是用户)。

单身人士是对象所以将它们用作对象,许多人通过调用返回它的方法直接访问单身人士,但这是有害的,因为你让你的代码知道对象是单身,我更喜欢使用单身作为对象,我传递它们通过构造函数,我将它们用作普通对象,通过这种方式,您的代码不知道这些对象是否是单例,这使得依赖关系更加清晰,并且有助于重构...

0
La VloZ Merrill

反用法:

单独使用过多的一个主要问题是该模式阻止了替代实现的轻松扩展和交换。在使用单例的任何地方,类名都是硬编码的。

0
Adam Franco

当我有一个封装了大量内存的类时,我觉得它们很有用。例如,在我最近一直在研究的游戏中,我有一个影响力地图类,其中包含一系列非常大的连续内存数组。我希望所有在启动时分配,所有在关机时释放,我绝对只需要它的一个副本。我也必须从很多地方访问它。我发现单例模式在这种情况下非常有用。

我确信还有其他解决方案,但我发现这个解决方案非常有用且易于实现。

0
Michael Avraamides

我认为这是 最强大的版本 对于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 找到这种模式。

0
artur02

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

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

我不明白为什么这个功能会很糟糕。

0
Zachary Kraus