web-dev-qa-db-fra.com

Singleton: comment devrait-il être utilisé

Edit: d’une autre question, j’ai fourni une réponse contenant des liens vers de nombreuses questions/réponses sur les singletons: Plus d’informations sur les singletons ici:

J'ai donc lu le fil Singletons: bon design ou une béquille?
Et l'argument fait toujours rage.

Je vois Singletons comme un modèle de conception (bon et mauvais).

Le problème avec Singleton n’est pas le motif mais plutôt les utilisateurs (désolé tout le monde). Tout le monde et son père pensent qu’ils peuvent l’appliquer correctement (et la plupart des gens ne le peuvent pas des nombreux entretiens que j’ai faits). De plus, parce que tout le monde pense pouvoir implémenter un Singleton correct, il abuse du Pattern et l’utilise dans des situations qui ne sont pas appropriées (en remplaçant les variables globales par des Singletons!).

Donc, les principales questions auxquelles il faut répondre sont:

  • Quand devriez-vous utiliser un Singleton
  • Comment implémenter correctement un Singleton

Mon souhait pour cet article est que nous puissions rassembler dans un seul endroit (plutôt que de devoir chercher sur Google et rechercher sur plusieurs sites) une source faisant autorité pour savoir quand (et ensuite comment) utiliser correctement un Singleton. Il serait également approprié de dresser une liste des anti-utilisations et des mauvaises implémentations courantes expliquant pourquoi ils ne fonctionnent pas et pour les bonnes implémentations leurs faiblesses.


Alors lancez le bal:
Je vais lever la main et dire que c’est ce que j’utilise mais il y a probablement des problèmes.
J'aime "Scott Myers" traiter le sujet dans ses livres "Effective C++"

Bonnes situations pour utiliser Singletons (pas beaucoup):

  • Cadres de journalisation
  • Filets de recyclage de fil
/*
 * 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;
        }
};

D'ACCORD. Permet d’obtenir des critiques et d’autres implémentations ensemble.
:-)

287
Martin York

Vous avez tous tort. Lisez la question. Répondre:

Utilisez un Singleton si:

  • Vous devez avoir un et un seul objet d'un type dans le système

Ne pas utiliser un Singleton si:

  • Vous voulez économiser de la mémoire
  • Vous voulez essayer quelque chose de nouveau
  • Vous voulez montrer à quel point vous savez
  • Parce que tout le monde le fait (voir programmeur culte du fret dans wikipedia)
  • Dans les widgets d'interface utilisateur
  • C'est supposé être une cache
  • Dans les cordes
  • En sessions
  • Je peux y aller toute la journée

Comment créer le meilleur singleton:

  • Le plus petit, mieux c'est. Je suis un minimaliste
  • Assurez-vous qu'il est thread-safe
  • Assurez-vous qu'il n'est jamais nul
  • Assurez-vous qu'il est créé qu'une seule fois
  • Paresseux ou initialisation du système? À la hauteur de vos exigences
  • Parfois, le système d'exploitation ou la machine virtuelle Java crée des singletons pour vous (par exemple, dans Java, chaque définition de classe est un singleton)
  • Fournir un destructeur ou trouver un moyen de disposer des ressources
  • Utilisez peu de mémoire
166
Javaxpert

Singletons vous donne la possibilité de combiner deux mauvais traits dans une classe. C'est faux dans à peu près tous les moyens.

Un singleton vous donne:

  1. Accès global à un objet, et
  2. Une garantie qu'un seul objet de ce type puisse être créé

Le numéro un est simple. Les globales sont généralement mauvaises. Nous ne devrions jamais rendre les objets globalement accessibles à moins que nous en ayons réellement besoin .

Le numéro deux peut sembler logique, mais réfléchissons-y. Quand est-ce que la dernière fois que vous ** avez accidentellement * créé un nouvel objet au lieu de référencer un objet existant? Puisque ceci est étiqueté C++, prenons un exemple de ce langage. Avez-vous souvent écrit accidentellement

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

Quand avez-vous eu l'intention d'écrire

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

Bien sûr que non. Nous n'avons pas besoin de protection contre cette erreur, car ce type d'erreur ne se produit tout simplement pas. Si tel est le cas, la bonne solution consiste à rentrer chez vous pendant 12 à 20 heures et à espérer que vous vous sentirez mieux.

Si un seul objet est nécessaire, créez simplement une instance. Si un objet doit être accessible globalement, faites-en un global. Mais cela ne signifie pas qu'il devrait être impossible de créer d'autres instances.

La contrainte "une seule instance est possible" ne nous protège pas vraiment contre les bogues probables. Mais cela rend notre code très difficile à refactoriser et à maintenir. Parce que assez souvent nous découvrons plus tard que nous avions besoin de plus d'une instance. Nous avons plus d’une base de données, nous avons plus d’une base de données objet de configuration, nous voulons plusieurs enregistreurs. Nos tests unitaires peuvent vouloir créer et recréer ces objets à chaque test, pour prendre un exemple commun.

Donc, un singleton devrait être utilisé si et seulement si, nous avons besoin à la fois des traits qu’il offre: Si nous avons besoin accès global (ce qui est rare, car les globaux sont généralement découragés) et nous avons besoin pour empêcher quiconque jamais de créer plus d'une instance d'une classe (ce qui me semble être un problème de conception). La seule raison pour laquelle je vois ceci est si la création de deux instances corromprait notre état d'application, probablement parce que la classe contient un certain nombre de membres statiques ou une sottise similaire. Dans ce cas, la réponse évidente est de réparer cette classe. Cela ne devrait pas dépendre d'être le seul cas.

Si vous avez besoin d'un accès global à un objet, faites-en une globale, comme std::cout. Mais ne limitez pas le nombre d'instances pouvant être créées.

Si vous devez absolument et positivement limiter le nombre d'instances d'une classe à une seule et qu'il est impossible de créer une seconde instance de manière sécurisée, appliquez-le. Mais ne le rendez pas globalement accessible également.

Si vous avez besoin des deux traits, alors 1) faites-en un singleton, et 2) dites-moi pourquoi vous en avez besoin, car j'ai du mal à imaginer un tel cas.

71
jalf

Le problème avec les singletons n'est pas leur mise en œuvre. C'est qu'ils associent deux concepts différents, dont aucun n'est évidemment souhaitable.

1) Les singletons fournissent un mécanisme d'accès global à un objet. Bien qu'ils puissent être légèrement plus sûrs en threads ou légèrement plus fiables dans les langues sans ordre d'initialisation bien défini, cet usage reste l'équivalent moral d'une variable globale. C'est une variable globale dont la syntaxe est maladroite (foo :: get_instance () au lieu de g_foo, par exemple), mais elle remplit exactement la même fonction (un seul objet accessible dans tout le programme) et présente les mêmes inconvénients.

2) Les singletons empêchent les instanciations multiples d'une classe. Il est rare, IME, que ce type de fonctionnalité soit intégré à une classe. C'est normalement une chose beaucoup plus contextuelle; beaucoup de choses qui sont considérées comme un-et-un sont en réalité des événements qui se produisent. IMO, une solution plus appropriée consiste à ne créer qu'une seule instance - jusqu'à ce que vous réalisiez que vous avez besoin de plusieurs instances.

35
DrPizza

Une chose avec des motifs: ne pas généraliser. Ils ont tous les cas où ils sont utiles et quand ils échouent.

Singleton peut être méchant quand vous devez tester le code. Vous êtes généralement coincé avec une instance de la classe et vous pouvez choisir entre ouvrir une porte dans le constructeur ou une méthode pour réinitialiser l'état, etc.

Un autre problème est que le Singleton n’est en réalité rien de plus qu’un variable globale déguisé. Lorsque vous avez trop d’états partagés mondiaux sur votre programme, les choses ont tendance à revenir en arrière, nous le savons tous.

Cela peut rendre suivi de dépendance plus difficile. Lorsque tout dépend de votre Singleton, il est difficile de le modifier, scindez-le en deux, etc. Vous êtes généralement pris avec. Cela entrave également la flexibilité. Enquêter sur un certain injection de dépendance pour tenter d’atténuer ce problème.

26
Paweł Hajdan

Les singletons vous permettent généralement d'avoir un état global complexe en langues, ce qui rend difficile, voire impossible, d'avoir des variables globales complexes.

Java en particulier utilise des singletons en remplacement des variables globales, car tout doit être contenu dans une classe. Les variables globales les plus proches sont les variables statiques publiques, qui peuvent être utilisées comme si elles étaient globales avec import static

C++ a des variables globales, mais l'ordre dans lequel les constructeurs de variables de classe globales sont invoqués n'est pas défini. En tant que tel, un singleton vous permet de différer la création d'une variable globale jusqu'à la première utilisation de cette variable.

Des langages tels que Python et Ruby utilisent très peu de singletons, car vous pouvez utiliser des variables globales dans un module.

Alors, quand est-il bon/mauvais d’utiliser un singleton? Presque exactement quand il serait bon/mauvais d’utiliser une variable globale.

12
Eli Courtwright
  • Comment implémenter correctement un Singleton

Il y a un problème que je n'ai jamais vu mentionné, quelque chose que j'ai rencontré dans un emploi précédent. Nous avions des singletons C++ partagés entre des DLL et la mécanique habituelle consistant à garantir qu'une seule instance d'une classe ne fonctionne tout simplement pas. Le problème est que chaque DLL obtient son propre ensemble de variables statiques, ainsi que le fichier EXE. Si votre fonction get_instance est inline ou fait partie d'une bibliothèque statique, chaque DLL se retrouvera avec sa propre copie du "singleton".

La solution consiste à s'assurer que le code de singleton n'est défini que dans un DLL ou EXE, ou de créer un gestionnaire de singleton avec ces propriétés pour répartir les instances.

6
Mark Ransom

Conception moderne en C++ by Alexandrescu possède un singleton générique fil-safe et pouvant être hérité.

Pour mes 2p, je pense qu'il est important d'avoir des durées de vie définies pour vos singletons (quand il est absolument nécessaire de les utiliser). Normalement, je ne laisse pas la fonction statique get() instancier quoi que ce soit et laisse la configuration et la destruction à une section dédiée de l'application principale. Cela permet de mettre en évidence les dépendances entre singletons - mais, comme souligné ci-dessus, il est préférable de les éviter si possible.

6
tenpn

Le premier exemple n'est pas thread-safe - si deux threads appellent getInstance en même temps, ce paramètre statique sera un PITA. Une certaine forme de mutex aiderait.

5
Rob

Comme d'autres l'ont noté, les inconvénients majeurs des singletons incluent l'incapacité de les étendre et la perte du pouvoir d'instancier plus d'une instance, par ex. à des fins de test.

Quelques aspects utiles des singletons:

  1. instanciation paresseuse ou initiale
  2. pratique pour un objet qui nécessite une configuration et/ou un état

Cependant, vous n'avez pas besoin d'utiliser un singleton pour obtenir ces avantages. Vous pouvez écrire un objet normal qui fait le travail, puis demander aux personnes d'y accéder via une fabrique (un objet séparé). L’usine peut se préoccuper de n’en instancier qu’un seul, et de le réutiliser, etc. De même, si vous programmez une interface plutôt qu’une classe concrète, l’usine peut utiliser des stratégies, c’est-à-dire que vous pouvez activer et désactiver diverses implémentations de l’interface.

Enfin, une usine se prête aux technologies d’injection de dépendance comme Spring, etc.

4
lexh

Etant donné qu'un singleton n'autorise la création que d'une seule instance, il contrôle efficacement la réplication d'instances. Par exemple, vous n'avez pas besoin de plusieurs occurrences d'une recherche - une carte de recherche en morse par exemple. Ainsi, l'envelopper dans une classe singleton est apt. Et le fait que vous ayez une seule instance de la classe ne signifie pas que vous êtes également limité en nombre de références à cette instance. Vous pouvez mettre en file d'attente les appels (pour éviter les problèmes de threading) à l'instance et effectuer les modifications nécessaires. Oui, la forme générale d'un singleton est publique, vous pouvez certainement modifier le design pour créer un singleton avec un accès plus restreint. Je ne l'ai pas fatigué auparavant, mais je sais que c'est possible. Et à tous ceux qui ont commenté en disant que le motif singleton est tout à fait mal, vous devriez le savoir: oui, c'est mal si vous ne l'utilisez pas correctement ou dans les limites d'une fonctionnalité efficace et d'un comportement prévisible: ne généralisez pas.

3
gogole

Les singletons sont pratiques lorsque vous avez beaucoup de code exécuté lors de l'initialisation et de l'objet. Par exemple, lorsque vous utilisez iBatis lorsque vous configurez un objet de persistance, il doit lire toutes les configurations, analyser les cartes, s’assurer que tout est correct, etc. avant d’obtenir votre code.

Si vous le faisiez à chaque fois, les performances seraient très dégradées. En l'utilisant dans un singleton, vous prenez ce coup une fois, puis tous les appels suivants n'ont pas à le faire.

3
Brian

La plupart des gens utilisent des singletons lorsqu'ils essaient de se sentir bien d'utiliser une variable globale. Il y a des utilisations légitimes, mais la plupart du temps, quand on les utilise, le fait qu'il ne puisse y avoir qu'un cas est un fait trivial par rapport au fait qu'il est accessible dans le monde entier.

3
Brad Barker

Le véritable échec des Singletons est qu’ils brisent l’héritage. Vous ne pouvez pas dériver une nouvelle classe pour vous donner des fonctionnalités étendues à moins d'avoir accès au code où le singleton est référencé. Ainsi, au-delà du fait que le Singleton rendra votre code étroitement couplé (réparable par un modèle de stratégie ... ou injection de dépendance), il vous empêchera également de fermer des sections du code de révision (bibliothèques partagées).

Ainsi, même les exemples de consignateurs ou de pools de threads ne sont pas valides et doivent être remplacés par des stratégies.

3
ZebZiggle

Mais quand j'ai besoin de quelque chose comme un Singleton, je finis souvent par utiliser un Schwarz Counter pour l'instancier.

2
Matt Cruikshank

J'utilise Singletons comme test d'entretien.

Lorsque je demande à un développeur de nommer des modèles de conception, si tout ce qu'ils peuvent nommer est Singleton, ils ne sont pas embauchés.

1
Matt Cruikshank

Vous trouverez ci-dessous la meilleure approche pour implémenter un motif singleton sécurisé pour les threads avec désallocation de la mémoire dans le destructeur lui-même. Mais je pense que le destructeur devrait être une option car l’instance singleton sera automatiquement détruite à la fin du programme:

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

Si nous voulons conserver l'état de l'instance tout au long de l'exécution du programme, si nous sommes impliqués dans l'écriture du journal d'exécution d'une application où une seule instance du fichier doit être sauvegardée. être utilisé .... et ainsi de suite. Ce sera appréciable si quelqu'un peut suggérer une optimisation dans le code ci-dessus.

1
A. Gupta

Le motif de singleton de Meyers fonctionne assez bien la plupart du temps et, dans certains cas, il n'est pas nécessairement rentable de chercher quelque chose de mieux. Tant que le constructeur ne lancera jamais et qu'il n'y a pas de dépendances entre singletons.

Un singleton est une implémentation pour un objet accessible globalement (GAO à partir de maintenant) bien que tous les GAO ne soient pas des singletons.

Les enregistreurs eux-mêmes ne devraient pas être des singletons, mais les moyens de journalisation devraient idéalement être accessibles de manière globale, afin de découpler l'endroit où le message de journal est généré, d'où et comment il est enregistré.

Le chargement paresseux/l'évaluation paresseuse est un concept différent et singleton l'implémente généralement aussi. Il pose de nombreux problèmes, en particulier la sécurité des threads, et des problèmes s’il échoue avec des exceptions telles que ce qui semblait être une bonne idée à l’époque se révèle finalement pas si génial. (Un peu comme l'implémentation de COW dans les chaînes).

Dans cet esprit, les GOA peuvent être initialisés comme ceci:

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

Cela n’a pas besoin d’être aussi grossier que cela, et clairement, dans une bibliothèque chargée contenant des objets, vous souhaitez probablement qu’un autre mécanisme gère leur durée de vie. (Placez-les dans un objet que vous obtenez lorsque vous chargez la bibliothèque).

Quand est-ce que j'utilise des singletons? Je les ai utilisées pour 2 choses - Une table singleton indiquant les bibliothèques chargées avec dlopen - Un gestionnaire de messages auquel les enregistreurs peuvent s'abonner et auquel vous pouvez envoyer des messages. Requis spécifiquement pour les gestionnaires de signaux.

0
CashCow

Si vous êtes celui qui a créé le singleton et qui l’utilise, ne le faites pas en tant que singleton (cela n’a aucun sens car vous pouvez contrôler la singularité de l’objet sans le rendre singleton), mais c’est logique quand vous développez une bibliothèque et vous souhaitez ne fournir qu’un seul objet à vos utilisateurs (dans ce cas, c’est vous qui avez créé le singleton, mais vous n’êtes pas l’utilisateur).

Les singletons sont des objets, alors utilisez-les en tant qu'objets. De nombreuses personnes accèdent aux singletons directement en appelant la méthode qui les renvoie, mais cela est dangereux car vous faites savoir à votre code que l'objet est singleton, je préfère utiliser les singletons en tant qu'objets, je les passe à travers le constructeur et je les utilise comme des objets ordinaires, de cette façon, votre code ne sait pas si ces objets sont des singletons ou non, ce qui rend les dépendances plus claires et aide un peu pour le refactoring ...

0
La VloZ Merrill

Anti-utilisation:

Un problème majeur lié à une utilisation excessive de singleton est que le modèle empêche une extension et une permutation faciles des implémentations alternatives. Le nom de la classe est codé en dur partout où le singleton est utilisé.

0
Adam Franco

Je les trouve utiles quand j'ai une classe qui encapsule beaucoup de mémoire. Par exemple, dans un jeu récent sur lequel je travaille, j'ai une classe de cartes d'influence qui contient une collection de très grands tableaux de mémoire contiguë. Je veux que tout soit alloué au démarrage, tout libéré à l’arrêt et je ne veux vraiment qu’un seul exemplaire. Je dois aussi y accéder de nombreux endroits. Je trouve le motif singleton très utile dans ce cas.

Je suis sûr qu'il existe d'autres solutions, mais celle-ci me semble très utile et facile à mettre en œuvre.

0
Michael Avraamides

Je pense que c'est la version la plus robuste pour 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();
      }
    }
  }
}

Voici la version . NET-optimisée :

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

Vous pouvez trouver ce modèle sur dotfactory.com .

0
artur02

Je ne comprends toujours pas pourquoi un singleton doit être mondial.

J'allais produire un singleton dans lequel je cachais une base de données à l'intérieur de la classe en tant que variable statique constante privée et créais des fonctions de classe qui utilisaient la base de données sans jamais exposer la base de données à l'utilisateur.

Je ne vois pas pourquoi cette fonctionnalité serait mauvaise.

0
Zachary Kraus