Sur les modèles de conception: quand dois-je utiliser le singleton?


448

La variable globale glorifiée - devient une classe mondiale glorifiée. Certains disent que la conception orientée objet est révolutionnaire.

Donnez-moi des scénarios, autres que le bon vieux logger où il est logique d'utiliser le singleton.


4
Depuis l'apprentissage de l'erlang, je préfère cette approche, à savoir l'immuabilité et la transmission des messages.
Setori

209
Qu'est-ce qui n'est pas constructif dans cette question? Je vois une réponse constructive ci-dessous.
mk12

3
Un framework d'injection de dépendances est un singleton très complexe qui donne un objet….
Ian Ringrose

1
Le singleton peut être utilisé comme un objet gestionnaire entre les instances d'autres objets, il ne doit donc y avoir qu'une seule instance du singleton où les autres instances doivent communiquer via l'instance du singleton.
Levent Divilioglu

J'ai une question secondaire: toute implémentation Singleton peut également être implémentée en utilisant une classe "statique" (avec une méthode "factory" / "init") - sans réellement créer une instance d'une classe (on pourrait dire qu'une classe statique est un sorte d'implémentation Singleton, mais ...) - pourquoi devrait-on utiliser un Singleton réel (une instance de classe unique qui garantit son unique) au lieu d'une classe statique? La seule raison à laquelle je peux penser est peut-être pour la "sémantique", mais même dans ce sens, les cas d'utilisation de Singleton ne nécessitent pas vraiment une relation "classe-> instance" par définition ... alors ... pourquoi?
Yuval A.

Réponses:


358

Dans ma quête de la vérité, j'ai découvert qu'il y a en fait très peu de raisons "acceptables" d'utiliser un Singleton.

Une raison qui revient souvent sur les internets est celle d'une classe de «journalisation» (que vous avez mentionnée). Dans ce cas, un Singleton peut être utilisé à la place d'une seule instance d'une classe car une classe de journalisation doit généralement être utilisée encore et encore ad nauseam par chaque classe d'un projet. Si chaque classe utilise cette classe de journalisation, l'injection de dépendances devient lourde.

La journalisation est un exemple spécifique d'un singleton "acceptable" car il n'affecte pas l'exécution de votre code. Désactivez la journalisation, l'exécution du code reste la même. Activez-le, même même. Misko le présente de la manière suivante dans Root Cause of Singletons : "Les informations circulent ici dans un sens: de votre application vers le logger. Même si les loggers sont à l'état global, car aucune information ne circule des loggers vers votre application, les loggers sont acceptables."

Je suis sûr qu'il existe également d'autres raisons valables. Alex Miller, dans " Patterns I Hate ", parle de localisateurs de services et d'interfaces utilisateur côté client étant également des choix "acceptables".

Pour en savoir plus sur Singleton, je vous aime, mais vous m'abaissez.


3
@ArneMertz Je suppose que c'est celui-là.
Attacktive

1
Pourquoi ne pouvez-vous pas simplement utiliser un objet global? Pourquoi faut-il que ce soit un singleton?
Shoe

1
Je pense que la méthode statique pour une utilisation de journalisation?
Skynet

1
Les singletons sont les meilleurs lorsque vous devez gérer des ressources. Par exemple, les connexions Http. Vous ne voulez pas établir 1 million de clients http pour un seul client, c'est un gaspillage fou et lent. Ainsi, un singleton avec un client http en pool de connexions sera beaucoup plus rapide et convivial.
Cogman

3
Je sais que c'est une vieille question et les informations contenues dans cette réponse sont excellentes. Cependant, j'ai du mal à comprendre pourquoi c'est la réponse acceptée lorsque l'OP a clairement spécifié: "Donnez-moi des scénarios, autres que le bon vieil enregistreur où il est logique d'utiliser le singleton."
Francisco C.

124

Un candidat Singleton doit satisfaire à trois exigences:

  • contrôle l'accès simultané à une ressource partagée.
  • l'accès à la ressource sera demandé à plusieurs parties disparates du système.
  • il ne peut y avoir qu'un seul objet.

Si votre Singleton proposé n'a qu'une ou deux de ces exigences, une refonte est presque toujours la bonne option.

Par exemple, il est peu probable qu'un spouleur d'imprimante soit appelé à partir de plusieurs emplacements (le menu Imprimer), vous pouvez donc utiliser des mutex pour résoudre le problème d'accès simultané.

Un enregistreur simple est l'exemple le plus évident d'un Singleton éventuellement valide, mais cela peut changer avec des schémas d'enregistrement plus complexes.


3
Je ne suis pas d'accord avec le point 2. Le point 3 n'est pas vraiment une raison (juste parce que vous le pouvez ne signifie pas que vous devriez) et 1 est un bon point mais je ne vois toujours pas l'utilité. Disons que la ressource partagée est un lecteur de disque ou un cache db. Vous pouvez ajouter un autre lecteur ou avoir un cache db se concentrant sur une autre chose (comme un cache pour une table spécialisée pour un thread, l'autre étant plus général).

15
Je pense que vous avez manqué le mot «candidat». Un candidat Singleton doit satisfaire aux trois exigences; ce n'est pas parce qu'un élément répond aux exigences qu'il doit s'agir d'un Singleton. Il peut y avoir d'autres facteurs de conception :)
metao

45

Lire les fichiers de configuration qui ne doivent être lus qu'au démarrage et les encapsuler dans un Singleton.


8
Similaire à Properties.Settings.Default.NET.
Nick Bedford

9
@Paul, le "camp sans singleton" indiquera que l'objet de configuration devrait simplement être passé dans les fonctions qui en ont besoin, au lieu de le rendre accessible globalement (aka singleton).
Pacerier

2
Être en désaccord. Si la configuration est déplacée vers la base de données, tout est vissé. Si le chemin vers la configuration dépend de quelque chose en dehors de ce singleton, ces éléments doivent également être statiques.
rr-

3
@PaulCroarkin Pouvez-vous développer et expliquer en quoi cela est bénéfique?
AlexG

1
@ rr- si la configuration se déplace vers la base de données, elle peut toujours être encapsulée dans un objet de configuration qui sera transmis aux fonctions qui en ont besoin. (PS je ne suis pas dans le camp "no-singleton").
Will Sheppard

36

Vous utilisez un singleton lorsque vous devez gérer une ressource partagée. Par exemple, un spouleur d'imprimante. Votre application ne doit avoir qu'une seule instance du spouleur afin d'éviter toute demande conflictuelle pour la même ressource.

Ou une connexion à une base de données ou un gestionnaire de fichiers, etc.


30
J'ai entendu cet exemple de spouleur d'imprimante et je pense que c'est un peu boiteux. Qui a dit que je ne pouvais pas avoir plus d'un spouleur? Qu'est-ce que c'est qu'un spouleur d'imprimante de toute façon? Que faire si j'ai différents types d'imprimantes qui ne peuvent pas entrer en conflit ou utiliser différents pilotes?
1800 INFORMATION

6
C'est juste un exemple ... pour toute situation que n'importe qui utilise comme exemple, vous pourrez trouver un design alternatif qui rend l'exemple inutile. Imaginons que le spouleur gère une seule ressource partagée par plusieurs composants. Ça marche.
Vincent Ramdhanie

2
C'est l'exemple classique du Gang of Four. Je pense qu'une réponse avec un vrai cas d'utilisation testé serait plus utile. Je veux dire une situation où vous pensiez que le Singleton est la meilleure solution.
Andrei Vajna II,

Une ressource partagée est à mon avis un exemple beaucoup trop large. Comment testeriez-vous que les objets utilisant le spouleur d'impression fonctionnent correctement face à un spouleur défectueux lorsque vous ne pouvez pas injecter une implémentation défectueuse de «spouleur»? bien que courte et peu informative, la réponse acceptée est une approche beaucoup plus sûre dans mon livre
Rune FS

Qu'est-ce qu'un spouleur d'imprimante?
RayLoveless

23

Les singletons en lecture seule stockant un état global (langue utilisateur, chemin d'accès au fichier d'aide, chemin d'application) sont raisonnables. Attention à utiliser des singletons pour contrôler la logique métier - le célibataire finit presque toujours par être multiple


4
La langue de l'utilisateur ne peut être singleton qu'avec l'hypothèse qu'un seul utilisateur peut utiliser le système.
Samuel Åslund

… Et qu'un utilisateur ne parle qu'une seule langue.
spectras

17

Gestion d'une connexion (ou d'un pool de connexions) à une base de données.

Je l'utiliserais également pour récupérer et stocker des informations sur des fichiers de configuration externes.


2
Un générateur de connexion à une base de données ne serait-il pas un exemple d'usine?
Ken

3
@Ken, vous voudriez que cette usine soit un singleton dans presque tous les cas.
Chris Marisic

2
@Federico, Le "camp sans singleton" indiquera que ces connexions à la base de données doivent simplement être transmises aux fonctions qui en ont besoin, au lieu de les rendre accessibles au niveau mondial (aka singleton).
Pacerier

3
Vous n'avez pas vraiment besoin d'un singleton pour cela. Il peut être injecté.
Nestor Ledon

11

L'une des façons d'utiliser un singleton est de couvrir une instance où il doit y avoir un seul "courtier" contrôlant l'accès à une ressource. Les singletons sont bons dans les enregistreurs car ils assurent l'accès à, disons, un fichier, qui ne peut être écrit qu'en exclusivité. Pour quelque chose comme la journalisation, ils fournissent un moyen d'abstraire les écritures dans quelque chose comme un fichier journal - vous pouvez envelopper un mécanisme de mise en cache dans votre singleton, etc.

Pensez également à une situation où vous avez une application avec de nombreuses fenêtres / threads / etc, mais qui nécessite un seul point de communication. Une fois, j'en ai utilisé un pour contrôler les travaux que je voulais lancer mon application. Le singleton était responsable de la sérialisation des travaux et de l'affichage de leur statut à toute autre partie du programme qui était intéressée. Dans ce genre de scénario, vous pouvez considérer un singleton comme une sorte de classe "serveur" s'exécutant dans votre application ... HTH


3
Les enregistreurs sont le plus souvent des singletons afin que les objets de journalisation n'aient pas à être contournés. Toute implémentation décente d'un flux de journaux garantira que les écritures simultanées sont impossibles, qu'il s'agisse d'un Singleton ou non.
metao

10

Un singleton doit être utilisé lors de la gestion de l'accès à une ressource partagée par l'ensemble de l'application, et il serait destructeur d'avoir potentiellement plusieurs instances de la même classe. S'assurer que l'accès aux ressources partagées en toute sécurité est un très bon exemple de cas où ce type de modèle peut être vital.

Lorsque vous utilisez Singletons, vous devez vous assurer que vous ne cachez pas accidentellement les dépendances. Idéalement, les singletons (comme la plupart des variables statiques dans une application) doivent être configurés pendant l'exécution de votre code d'initialisation pour l'application (static void Main () pour les exécutables C #, static void main () pour les exécutables java), puis transmis à toutes les autres classes instanciées qui en ont besoin. Cela vous aide à maintenir la testabilité.


8

Je pense que l'utilisation de singleton peut être considérée comme la même chose que la relation plusieurs-à-un dans les bases de données. Si vous avez de nombreuses parties différentes de votre code qui doivent fonctionner avec une seule instance d'un objet, c'est là qu'il est logique d'utiliser des singletons.


6

Un exemple pratique d'un singleton peut être trouvé dans Test :: Builder , la classe qui sauvegarde à peu près tous les modules de test Perl modernes. Le test :: Builder singleton stocke et organise l'état et l'historique du processus de test (résultats de test historiques, compte le nombre de tests exécutés) ainsi que des éléments comme la destination de la sortie de test. Ils sont tous nécessaires pour coordonner plusieurs modules de test, écrits par différents auteurs, pour travailler ensemble dans un seul script de test.

L'histoire du singleton Test :: Builder est pédagogique. L'appel new()vous donne toujours le même objet. Tout d'abord, toutes les données ont été stockées en tant que variables de classe sans rien dans l'objet lui-même. Cela a fonctionné jusqu'à ce que je veuille tester Test :: Builder avec lui-même. Ensuite, j'avais besoin de deux objets Test :: Builder, un configuré comme un mannequin, pour capturer et tester son comportement et sa sortie, et un pour être le véritable objet de test. À ce stade, Test :: Builder a été refactorisé en un objet réel. L'objet singleton était stocké en tant que données de classe et le new()renvoyait toujours. create()a été ajouté pour créer un nouvel objet et activer les tests.

Actuellement, les utilisateurs souhaitent modifier certains comportements de Test :: Builder dans leur propre module, mais en laisser d'autres, tandis que l'historique des tests reste commun à tous les modules de test. Ce qui se passe maintenant, c'est que l'objet Test :: Builder monolithique est décomposé en morceaux plus petits (historique, sortie, format ...) avec une instance Test :: Builder les rassemblant. Maintenant, Test :: Builder ne doit plus être un singleton. Ses composants, comme l'histoire, peuvent l'être. Cela pousse la nécessité inflexible d'un singleton à un niveau inférieur. Il donne plus de flexibilité à l'utilisateur pour mélanger et assortir les morceaux. Les objets singleton plus petits peuvent désormais simplement stocker des données, avec leurs objets conteneurs qui décident comment les utiliser. Il permet même à une classe non-Test :: Builder de jouer en utilisant l'historique Test :: Builder et les singletons de sortie.

Il semble y avoir un push and pull entre la coordination des données et la flexibilité du comportement qui peut être atténuée en mettant le singleton autour des données partagées avec le plus petit nombre de comportements possible pour garantir l'intégrité des données.


5

Lorsque vous chargez un objet Propriétés de configuration, à partir de la base de données ou d'un fichier, il est utile de l'avoir comme singleton; il n'y a aucune raison de continuer à relire les données statiques qui ne changeront pas pendant le fonctionnement du serveur.


2
Pourquoi ne chargeriez-vous pas simplement les données une fois et transmettriez-vous l'objet de configuration selon vos besoins?
lagweezle

qu'est-ce qui se passe avec le passage ??? Si je devais faire circuler tous les objets dont j'ai besoin, j'aurais des constructeurs avec 20 arguments ...
Enerccio

@Enerccio Si vous avez des objets qui dépendent de 20 autres sans encapsulation, vous avez déjà des problèmes de conception majeurs.
spectras

@spectras Dois-je? Si j'implémente une boîte de dialogue gui, j'aurai besoin de: référentiel, localisation, données de session, données d'application, parent de widget, données client, gestionnaire d'autorisations et probablement plus. Bien sûr, vous pouvez en agréger, mais pourquoi? Personnellement, j'utilise spring et aspects pour simplement câbler automatiquement toutes ces dépendances dans la classe widget et qui découplent tout.
Enerccio

Si vous avez un tel état, vous pouvez envisager de mettre en œuvre une façade, donnant une vue des aspects pertinents du contexte spécifique. Pourquoi? Parce qu'il permettrait une conception propre sans les antipatterns du constructeur singleton ou 29-arg. En fait, le fait même que votre boîte de dialogue gui accède à toutes ces choses crie "violation du principe de responsabilité unique".
spectras

3

Comme tout le monde l'a dit, une ressource partagée - en particulier quelque chose qui ne peut pas gérer l'accès simultané.

Un exemple spécifique que j'ai vu est un Lucene Search Index Writer.


1
Et pourtant, IndexWriter n'est pas un singleton ...
Mark

3

Vous pouvez utiliser Singleton lors de l'implémentation du modèle d'état (de la manière indiquée dans le livre GoF). En effet, les classes d'état concrètes n'ont pas d'état propre et exécutent leurs actions en termes de classe de contexte.

Vous pouvez également faire de Abstract Factory un singleton.


C'est le cas que je traite maintenant dans un projet. J'ai utilisé un modèle d'état pour supprimer le code conditionnel répétitif des méthodes du contexte. Les états n'ont pas de variables d'instance qui leur soient propres. Cependant, je suis sur la clôture en ce qui concerne si je dois les faire singletons. Chaque fois que l'état bascule, une nouvelle instance est instanciée. Cela semble inutile car il n'y a aucun moyen que l'instance puisse être différente d'une autre (car il n'y a pas de variables d'instance). J'essaie de comprendre pourquoi je ne devrais pas l'utiliser.
kiwicomb123

1
@ kiwicomb123 Essayez de rendre votre setState()responsable de décider de la politique de création d'état. Cela aide si votre langage de programmation prend en charge les modèles ou les génériques. Au lieu de Singleton, vous pouvez utiliser le modèle Monostate , où l'instanciation d'un objet d'état finit par réutiliser le même objet d'état global / statique. La syntaxe pour changer l'état peut rester inchangée, car vos utilisateurs n'ont pas besoin de savoir que l'état instancié est un Monostate.
Emile Cormier

D'accord, dans mes états, je pourrais simplement rendre toutes les méthodes statiques, donc chaque fois qu'une nouvelle instance est créée, elle n'a pas le même temps système? Je suis un peu confus, j'ai besoin de lire sur le modèle Monostate.
kiwicomb123

@ kiwicomb123 Non, Monostate ne consiste pas à rendre tous les membres statiques. Mieux vaut que vous le lisiez, puis vérifiez SO pour les questions et réponses connexes.
Emile Cormier

Je pense que cela devrait avoir plus de votes. L'usine abstraite est assez courante et comme les usines sont sans état, stables dans l'état et ne peuvent pas être implémentées avec des méthodes statiques (en Java) qui ne sont pas remplacées, l'utilisation de singleton devrait être acceptable.
DPM

3

Ressources partagées. Surtout en PHP, une classe de base de données, une classe de modèle et une classe de dépôt de variable globale. Tous doivent être partagés par tous les modules / classes utilisés dans le code.

C'est une véritable utilisation d'objet -> la classe de modèle contient le modèle de page en cours de création, et il est façonné, ajouté, modifié par les modules qui s'ajoutent à la sortie de la page. Il doit être conservé comme une seule instance afin que cela puisse se produire, et il en va de même pour les bases de données. Avec un singleton de base de données partagée, toutes les classes de modules peuvent accéder aux requêtes et les obtenir sans avoir à les réexécuter.

Un dépôt de variable global singleton vous fournit un dépôt de variable global, fiable et facilement utilisable. Cela nettoie beaucoup votre code. Imaginez avoir toutes les valeurs de configuration dans un tableau dans un singleton comme:

$gb->config['hostname']

ou avoir toutes les valeurs de langue dans un tableau comme:

$gb->lang['ENTER_USER']

À la fin de l'exécution du code de la page, vous obtenez, disons, une version désormais mature:

$template

Singleton, un $gbsingleton qui a le tableau lang pour le remplacer, et toutes les sorties sont chargées et prêtes. Il vous suffit de les remplacer par les clés qui sont désormais présentes dans la valeur de page de l'objet modèle mature, puis de les diffuser à l'utilisateur.

Le grand avantage de ceci est que vous pouvez faire N'IMPORTE QUEL post-traitement que vous aimez sur n'importe quoi. Vous pouvez diriger toutes les valeurs de langue vers google translate ou un autre service de traduction et les récupérer et les replacer à leur place, traduites, par exemple. ou, vous pouvez remplacer dans les structures de page, ou, les chaînes de contenu, comme vous le souhaitez.


21
Vous souhaiterez peut-être diviser votre réponse en plusieurs paragraphes et bloquer les segments de code pour plus de lisibilité.
Justin

1

Il peut être très pragmatique de configurer des problèmes d'infrastructure spécifiques en tant que singletons ou variables globales. Mon exemple préféré est celui des frameworks d'injection de dépendance qui utilisent des singletons pour servir de point de connexion au framework.

Dans ce cas, vous dépendez de l'infrastructure pour simplifier l'utilisation de la bibliothèque et éviter une complexité inutile.


0

Je l'utilise pour un objet encapsulant des paramètres de ligne de commande lorsqu'il s'agit de modules enfichables. Le programme principal ne sait pas quels sont les paramètres de ligne de commande pour les modules qui sont chargés (et ne sait pas toujours quels modules sont chargés). par exemple, charge principale A, qui n'a pas besoin de paramètres lui-même (alors pourquoi cela devrait prendre un pointeur / référence / autre, je ne suis pas sûr - ressemble à de la pollution), puis charge les modules X, Y et Z. Deux de ceux-ci, disons X et Z, ont besoin (ou acceptent) de paramètres, donc ils rappellent au singleton de ligne de commande pour lui dire quels paramètres accepter, et à l'exécution ils rappellent pour savoir si l'utilisateur a réellement spécifié d'eux.

À bien des égards, un singleton pour gérer les paramètres CGI fonctionnerait de manière similaire si vous n'utilisez qu'un seul processus par requête (les autres méthodes mod_ * ne le font pas, donc ce serait mauvais là-bas - donc l'argument qui dit que vous ne devriez pas '' t utilisez des singletons dans le monde mod_cgi au cas où vous porteriez vers le mod_perl ou autre monde).


-1

Un exemple avec du code, peut-être.

Ici, ConcreteRegistry est un singleton dans un jeu de poker qui permet aux comportements tout au long de l'arborescence des packages d'accéder aux quelques interfaces principales du jeu (c'est-à-dire les façades du modèle, de la vue, du contrôleur, de l'environnement, etc.):

http://www.edmundkirwan.com/servlet/fractal/cs1/frac-cs40.html

Ed.


1
Le lien est maintenant rompu, mais si vous enregistrez des informations de vue dans un singleton, qui sera accessible tout au long de l'application, vous manquez le point de MVC. Une vue est mise à jour par (et communique avec) un contrôleur, qui utilise le modèle. Comme cela semble ici, c'est probablement une mauvaise utilisation de Singleton et une refactorisation s'impose.
drharris

-9

1 - Un commentaire sur la première réponse:

Je ne suis pas d'accord avec une classe Logger statique. cela peut être pratique pour une implémentation, mais il ne peut pas être remplacé pour des tests unitaires. Une classe statique ne peut pas être remplacée par un double test. Si vous ne faites pas de test unitaire, vous ne verrez pas le problème ici.

2 - J'essaie de ne pas créer un singleton à la main. Je viens de créer un objet simple avec des constructeurs qui me permettent d'injecter des collaborateurs dans l'objet. Si j'avais besoin d'un singleton, j'utiliserais un framework d'inyection de dépendances (Spring.NET, Unity pour .NET, Spring pour Java), ou un autre.


11
Vous devez commenter les réponses directement en cliquant sur le lien au bas d'une réponse; c'est beaucoup plus facile à lire de cette façon. De plus, la réponse que vous avez vue en haut n'est probablement pas la première. Les réponses sont réorganisées tout le temps.
Ross

pourquoi voudriez-vous une journalisation des tests unitaires?
Enerccio

Il existe une énorme différence entre "une classe de Logger statique" et une instance de Logger statique . Le modèle singleton ne dit pas «rendre votre classe statique», il dit de rendre statique l'accès à l'instance d'un objet. Ainsi, par exemple, ILogger logger = Logger.SingleInstance();lorsque cette méthode est statique et renvoie une instance stockée de manière statique d'un ILogger. Vous avez utilisé l'exemple d'un «framework d'injection de dépendances». Presque tous les conteneurs DI sont des singletons; leurs configurations sont définies de façon statique et finalement accessibles à partir / stockées dans une interface de fournisseur de services unique.
Jon Davis
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.