Singleton: Comment devrait-il être utilisé

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

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

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

Le problème avec Singleton n’est pas le Pattern mais plutôt les utilisateurs (désolé tout le monde). Tout le monde et leur père pensent pouvoir en appliquer un correctement (et d’après les nombreuses interviews que j’ai réalisées, la plupart des gens ne peuvent pas). Aussi, parce que tout le monde pense pouvoir implémenter un Singleton correct, ils abusent du Pattern et l’utilisent dans des situations inappropriées (en remplaçant les variables globales par Singletons!).

Les principales questions auxquelles il faut répondre sont les suivantes:

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

Mon espoir pour cet article est que nous pouvons collecter ensemble dans un seul endroit (plutôt que d’avoir à google et rechercher plusieurs sites) une source faisant autorité de quand (et comment) utiliser un Singleton correctement. Une liste appropriée d’anti-Usages et de mauvaises implémentations communes expliquant pourquoi elles ne fonctionnent pas et pour de bonnes implémentations leurs faiblesses.


Alors lancez-vous:
Je vais lever la main et dire que c’est ce que j’utilise mais que j’ai probablement des problèmes.
J’aime bien “Scott Myers” manipuler le sujet dans ses livres “Effective C ++”

Bonnes Situations à utiliser Singletons (pas beaucoup):

  • Cadres de journalisation
  • Fil de recyclage des piscines
/* * 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 comstackr 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. Obtient quelques critiques et autres implémentations ensemble.
🙂

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

Utilisez un singleton si:

  • Vous devez avoir un et un seul object d’un type dans le système

N’utilisez pas un Singleton si:

  • Vous voulez économiser de la mémoire
  • Vous voulez essayer quelque chose de nouveau
  • Vous voulez montrer combien vous savez
  • Parce que tout le monde le fait (voir le programmeur de culte cargo dans Wikipedia)
  • Dans les widgets de l’interface utilisateur
  • C’est censé être un cache
  • En cordes
  • En sessions
  • Je peux aller toute la journée

Comment créer le meilleur singleton:

  • Le plus petit, le meilleur. Je suis un minimaliste
  • Assurez-vous que le fil est sans danger
  • Assurez-vous qu’il n’est jamais nul
  • Assurez-vous qu’il est créé une seule fois
  • Paresseux ou initialisation du système? À la hauteur de vos exigences
  • Parfois, le système d’exploitation ou la JVM crée des singletons pour vous (par exemple, en Java, chaque définition de classe est un singleton)
  • Fournir un destructeur ou déterminer comment se débarrasser des ressources
  • Utilisez peu de mémoire

Les singletons vous permettent de combiner deux mauvais traits dans une classe. C’est faux dans presque tous les sens.

Un singleton vous donne:

  1. Accès global à un object et
  2. Une garantie que plus d’un object de ce type ne pourra jamais être créé

Le numéro un est simple. Les globaux sont généralement mauvais. Nous ne devrions jamais rendre les objects globalement accessibles, sauf si nous en avons vraiment besoin.

Le numéro deux peut sembler logique, mais réfléchissons-y. Quand est-ce que la dernière fois que vous avez créé un nouvel object par inadvertance au lieu de référencer un object existant? Comme il est marqué C ++, utilisons un exemple de ce langage. Est-ce que vous écrivez souvent accidentellement

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

Quand tu voulais écrire

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

Bien sûr que non. Nous n'avons pas besoin de protection contre cette erreur, car ce genre d'erreur ne se produit tout simplement pas. Si c'est le cas, la bonne réponse est de rentrer à la maison et de dormir pendant 12 à 20 heures et espère que vous vous sentirez mieux.

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

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 à restructurer et à maintenir. Parce que souvent nous découvrons plus tard que nous avons eu besoin de plus d'une instance. Nous avons plusieurs bases de données, nous avons plusieurs objects de configuration, nous voulons plusieurs enregistreurs. Nos tests unitaires peuvent vouloir créer et recréer ces objects à chaque test, pour prendre un exemple commun.

Donc, un singleton devrait être utilisé si et seulement si, nous avons besoin des deux caractéristiques qu'il offre: Si nous avons besoin d' un access global (ce qui est rare car les globaux sont généralement déconseillés) et d'empêcher quiconque de créer plus d'une instance d'un classe (qui me semble être un problème de conception). La seule raison pour laquelle je peux voir pour cela est que 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 stupidité similaire. Dans ce cas, la réponse évidente consiste à corriger cette classe. Cela ne devrait pas dépendre d'être la seule instance.

Si vous avez besoin d'un access global à un object, faites-en un global, comme std::cout . Mais ne limitez pas le nombre d'instances pouvant être créées.

Si vous avez absolument besoin de limiter le nombre d'instances d'une classe à un seul, et qu'il est impossible de créer une seconde instance en toute sécurité, appliquez-la. Mais ne le rendez pas accessible à tous.

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

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

1) Les singletons fournissent un mécanisme d’access global à un object. Bien qu’ils puissent être légèrement plus sûrs ou marginalement plus fiables dans les langues sans ordre d’initialisation bien défini, cet usage rest l’équivalent moral d’une variable globale. C’est une variable globale déguisée dans une syntaxe peu pratique (foo :: get_instance () au lieu de g_foo, par exemple), mais elle remplit exactement le même objective (un seul object 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 une et une seule sont vraiment des cas. Une solution plus appropriée consiste à créer une seule instance – jusqu’à ce que vous réalisiez que vous avez besoin de plusieurs instances.

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

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

L’autre problème est que le Singleton n’est en fait rien de plus qu’une variable globale déguisée. Lorsque vous avez trop d’état partagé sur votre programme, les choses ont tendance à revenir, nous le soaps tous.

Cela peut rendre le suivi des dépendances plus difficile. Lorsque tout dépend de votre Singleton, il est plus difficile de le changer, de le diviser en deux, etc. Cela entrave également la flexibilité. Examinez un cadre d’ dependency injection pour tenter de résoudre ce problème.

Les singletons vous permettent essentiellement d’avoir un état global complexe dans des langages qui rendent difficile ou impossible la présence de variables globales complexes.

Java en particulier utilise 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 ++ possède des variables globales, mais l’ordre dans lequel les constructeurs de variables de classe globales sont appelés est indéfini. En tant que tel, un singleton vous permet de reporter la création d’une variable globale jusqu’à la première fois que cette variable est nécessaire.

Des langages tels que Python et Ruby utilisent très peu les singletons, car vous pouvez utiliser des variables globales au sein d’un module.

Alors, quand est-ce bon / mauvais d’utiliser un singleton? À peu près exactement quand il serait bon d’utiliser une variable globale.

La conception moderne de C ++ par Alexandrescu comporte un singleton générique, héritable des threads.

Pour ma valeur de 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 l’installation et la destruction dans une section dédiée de l’application principale. Cela aide à mettre en évidence les dépendances entre singletons – mais, comme souligné ci-dessus, il est préférable de les éviter si possible.

  • Comment implémentez-vous un Singleton correctement?

Il y a un problème que je n’ai jamais vu, quelque chose que j’ai rencontré lors d’un emploi précédent. Nous avions des singletons C ++ partagés entre les DLL, et les mécanismes habituels pour assurer une instance unique d’une classe ne fonctionnaient 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 en ligne ou fait partie d’une bibliothèque statique, chaque DLL se retrouvera avec sa propre copie du “singleton”.

La solution consiste à vérifier que le code singleton est uniquement défini dans une DLL ou un fichier EXE ou à créer un gestionnaire singleton avec ces propriétés pour répartir les instances.

Le premier exemple n’est pas thread-safe – si deux threads appellent getInstance en même temps, cette statique sera un PITA. Une forme de mutex aiderait.

Comme d’autres l’ont noté, l’incapacité à les étendre et à perdre le pouvoir d’instancier plus d’une instance, par exemple à des fins de test, constitue un inconvénient majeur pour les singletons.

Quelques aspects utiles de singletons:

  1. instanciation paresseuse ou initiale
  2. pratique pour un object nécessitant une configuration et / ou un état

Cependant, vous n’avez pas besoin d’utiliser un singleton pour obtenir ces avantages. Vous pouvez écrire un object normal qui effectue le travail, puis demander aux utilisateurs d’y accéder via une fabrique (un object distinct). L’usine peut s’inquiéter de l’instanciation et de la réutilisation, etc., le cas échéant. De plus, si vous programmez une interface plutôt qu’une classe concrète, la fabrique 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 à des technologies d’dependency injection comme Spring, etc.

Les singletons sont pratiques lorsque vous utilisez un code de lot lors de l’initialisation et de l’object. Par exemple, lorsque vous utilisez iBatis lorsque vous configurez un object de persistance, il doit lire toutes les configurations, parsingr les cartes, vous assurer que tout est correct, etc., avant de vous rendre à votre code.

Si vous le faisiez à chaque fois, la performance serait très dégradée. En l’utilisant dans un singleton, vous prenez ce coup une fois et tous les appels suivants ne sont pas obligés de le faire.

La véritable perte de Singletons est qu’ils brisent l’inheritance. Vous ne pouvez pas dériver une nouvelle classe pour vous donner des fonctionnalités étendues, sauf si vous avez access 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, aussi appelé dependency injections), il vous empêchera également de fermer des sections du code de la révision (bibliothèques partagées).

Ainsi, même les exemples de loggers ou de pools de threads ne sont pas valides et doivent être remplacés par Strategies.

La plupart des gens utilisent les singletons lorsqu’ils essaient de se sentir bien dans l’utilisation d’une variable globale. Il y a des utilisations légitimes, mais la plupart du temps, quand les gens les utilisent, le fait qu’il ne puisse y avoir qu’une seule instance est un fait sortingvial comparé au fait qu’il est accessible à l’échelle mondiale.

Un singleton autorisant uniquement la création d’une instance, il contrôle efficacement la réplication d’instance. Par exemple, vous n’auriez pas besoin de plusieurs instances d’une recherche – une mappe de recherche morse par exemple, ce qui permet de l’envelopper dans une classe singleton. Et le fait que vous ayez une seule instance de la classe ne signifie pas que vous êtes également limité par le nombre de références à cette instance. Vous pouvez mettre en queue 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 globalement publique, vous pouvez certainement modifier la conception pour créer un singleton plus restreint d’access. Je n’ai pas fatigué cela avant, mais je sais que c’est possible. Et à tous ceux qui ont commenté en disant que le singleton est complètement diabolique, vous devez le savoir: oui, c’est mal si vous ne l’utilisez pas correctement ou s’il contient des fonctionnalités efficaces et un comportement prévisible: ne pas généraliser.

Mais quand j’ai besoin de quelque chose comme un singleton, je finis souvent par utiliser un compteur Schwarz pour l’instancier.

J’utilise Singletons comme test d’interview.

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

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

 #include #include 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 lock(mtx); if (singletonInstance == NULL) singletonInstance = new MySingleton(); } return singletonInstance; } 

En ce qui concerne les situations où nous devons utiliser des classes singleton, nous pouvons être – Si nous voulons conserver l’état de l’instance pendant l’exécution du programme Si nous sums impliqués dans l’écriture dans le journal d’exécution d’une application où une seule instance du fichier doit être utilisé …. et ainsi de suite. Si quelqu’un peut suggérer une optimisation dans le code ci-dessus, cela sera appréciable.

Anti-utilisation:

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

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 optimisée pour .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(); } } } } 

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

Le modèle singleton de Meyers fonctionne assez bien la plupart du temps, et dans les cas où cela ne se produit pas, il n’est pas forcément préférable de chercher quelque chose de mieux. Tant que le constructeur ne lancera jamais et qu’il n’y a pas de dépendances entre les singletons.

Un singleton est une implémentation d’un object accessible globalement (GAO désormais) 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 connexion devraient idéalement être accessibles globalement, afin de découpler où le message de journal est généré de l’endroit ou de la manière dont il est consigné.

L’évaluation paresseuse / chargement différé est un concept différent et singleton le met également en œuvre. Il se heurte à de nombreux problèmes, en particulier la sécurité des threads et les problèmes s’il échoue avec des exceptions telles que ce qui semblait être une bonne idée à l’époque ne serait pas si bon après tout. (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 fait aussi grossièrement que cela, et dans une bibliothèque chargée contenant des objects, vous voudrez probablement un autre mécanisme pour gérer leur durée de vie. (Put them in an object that you get when you load the library).

As for when I use singletons? I used them for 2 things – A singleton table that indicates what libraries have been loaded with dlopen – A message handler that loggers can subscribe to and that you can send messages to. Required specifically for signal handlers.

I still don’t get why a singleton has to be global.

I was going to produce a singleton where I hid a database inside the class as a private constant static variable and make class functions that utilize the database without ever exposing the database to the user.

I don’t see why this functionality would be bad.

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.

If you are the one who created the singleton and who uses it, dont make it as singleton (it doesn’t have sense because you can control the singularity of the object without making it singleton) but it makes sense when you a developer of a library and you want to supply only one object to your users (in this case you are the who created the singleton, but you aren’t the user).

Singletons are objects so use them as objects, many people accesses to singletons directly through calling the method which returns it, but this is harmful because you are making your code knows that object is singleton, I prefer to use singletons as objects, I pass them through the constructor and I use them as ordinary objects, by that way, your code doesn’t know if these objects are singletons or not and that makes the dependencies more clear and it helps a little for refactoring …

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