Quand Singleton est-il approprié? [fermé]

Réponses:

32

Les deux principales critiques des singletons se divisent en deux camps d'après ce que j'ai observé:

  1. Les singletons sont mal utilisés et maltraités par des programmeurs moins compétents. Ainsi, tout devient un singleton et vous voyez du code jonché de références Class :: get_instance (). En règle générale, seules une ou deux ressources (par exemple, une connexion à une base de données) peuvent être utilisées pour utiliser le modèle Singleton.
  2. Les singletons sont essentiellement des classes statiques reposant sur une ou plusieurs méthodes et propriétés statiques. Toutes les choses statiques posent des problèmes réels et concrets lorsque vous essayez de faire des tests unitaires, car elles représentent des impasses dans votre code qui ne peuvent être ni moquées ni écrites. Par conséquent, lorsque vous testez une classe qui repose sur un Singleton (ou toute autre méthode ou classe statique), vous testez non seulement cette classe, mais également la méthode ou la classe statique.

Par conséquent, une approche courante consiste à créer un objet conteneur large pour contenir une instance unique de ces classes. Seul l'objet conteneur modifie ces types de classes, tandis que de nombreuses autres classes peuvent en obtenir l'accès. l'objet conteneur.

Noah Goodrich
la source
6
Je voudrais juste dire que vous pouvez vous moquer des méthodes statiques en Java avec JMockit ( code.google.com/p/jmockit ). C'est un outil très pratique.
Michael K
3
Quelques réflexions: le conteneur est un Singleton, vous ne vous êtes donc pas débarrassé de Singletons. De même, si vous écrivez une bibliothèque avec une API publique, vous ne pouvez pas forcer l'utilisateur de votre API à utiliser votre conteneur. Si vous avez besoin d'un gestionnaire de ressources global dans votre bibliothèque (pour protéger l'accès à une seule ressource physique, par exemple), vous devez utiliser un Singleton.
Scott Whitlock
2
@ Scott Whitlock, pourquoi faut-il être un singleton? Ce n’est pas parce qu’il n’ya qu’une seule instance. Toute bibliothèque IoC gérera ce type de dépendance ...
MattDavey le
Cette solution la proposait également sous le nom de Toolbox
cregox
41

Je conviens que c'est un anti-modèle. Pourquoi? Parce qu'il permet à votre code de mentir sur ses dépendances, et vous ne pouvez pas faire confiance aux autres programmeurs pour ne pas introduire d'état mutable dans vos singletons précédemment immuables.

Une classe peut avoir un constructeur qui ne prend qu'une chaîne, vous pensez donc qu'elle est instanciée de manière isolée et qu'elle n'a pas d'effets secondaires. Cependant, silencieusement, il communique avec une sorte d'objet singleton public et disponible de manière globale, afin que chaque fois que vous instanciez la classe, celle-ci contienne des données différentes. C'est un gros problème, non seulement pour les utilisateurs de votre API, mais également pour la testabilité du code. Pour tester correctement le code par unité, vous devez microgérer et connaître l'état global dans le singleton, pour obtenir des résultats de test cohérents.

Magnus Wolffelt
la source
Si seulement je pouvais voter plus d'une fois!
MattDavey
34

Le modèle Singleton est fondamentalement juste une variable globale initialisée paresseusement. Les variables globales sont généralement et à juste titre considérées comme mauvaises, car elles permettent une action fantasmagorique à une distance séparant des parties apparemment non liées d'un programme. Cependant, à mon humble avis, les variables globales définies une seule fois, d’un endroit à la routine d’initialisation d’un programme (par exemple, en lisant un fichier de configuration ou en arguments de ligne de commande) et traitées comme des constantes par la suite sont tout à fait acceptables. Une telle utilisation de variables globales n’est différente que par lettre et non par esprit de la déclaration d’une constante nommée au moment de la compilation.

De même, mon opinion sur les singletons est qu’ils sont mauvais si et seulement si ils sont utilisés pour passer d’un état mutable à l’autre entre des parties apparemment non liées d’un programme. S'ils ne contiennent pas d'état mutable, ou si l'état mutable qu'ils contiennent est complètement encapsulé afin que les utilisateurs de l'objet ne soient pas obligés de le savoir, même dans un environnement multithread, il n'y a aucun problème avec eux.

Dsimcha
la source
3
En d'autres termes, vous êtes opposé à toute utilisation d'une base de données dans un programme.
Kendall Helmstetter Gelner le
Il existe une célèbre vidéo google tech talk qui partage votre opinion youtube.com/watch?v=-FRm3VPhseI
Martin York
2
@KendallHelmstetterGelner: il existe une différence entre le stockage d'état et le stockage secondaire. Il est peu probable que vous puissiez conserver une base de données complète en mémoire.
Martin York
7

Pourquoi les gens l'utilisent-ils?

J'ai vu pas mal de singletons dans le monde de PHP. Je ne me souviens d'aucun cas d'utilisation où j'ai trouvé le modèle justifié. Mais je pense avoir une idée de la motivation pour laquelle les gens l’utilisaient.

  1. Instance unique .

    "Utilisez une seule instance de classe C dans l’application."

    C'est une exigence raisonnable, par exemple pour la "connexion de base de données par défaut". Cela ne signifie pas que vous ne créerez jamais une deuxième connexion à la base de données, cela signifie simplement que vous travaillez habituellement avec la connexion par défaut.

  2. Instanciation unique .

    "N'autorisez pas l'instanciation de la classe C plus d'une fois (par processus, par requête, etc.)."

    Ceci n'est pertinent que si l'instanciation de la classe aurait des effets secondaires en conflit avec d'autres instances.

    Souvent, ces conflits peuvent être évités en modifiant la conception du composant, par exemple en éliminant les effets secondaires du constructeur de la classe. Ou ils peuvent être résolus d'une autre manière. Mais il pourrait toujours y avoir des cas d'utilisation légitimes.

    Vous devez également vous demander si le critère "un seul" signifie réellement "un par processus". Par exemple, pour la simultanéité des ressources, l'exigence est plutôt "un par système entier, pour tous les processus" et non "un par processus". Et pour d’autres choses, c’est plutôt par «contexte d’application», et vous n’avez qu’un seul contexte d’application par processus.

    Sinon, il n'est pas nécessaire d'appliquer cette hypothèse. Cela signifie également que vous ne pouvez pas créer une instance distincte pour les tests unitaires.

  3. Accès global.

    Cela n’est légitime que si vous n’avez pas l’infrastructure appropriée pour transférer des objets à l’endroit où ils sont utilisés. Cela pourrait signifier que votre environnement ou votre environnement est nul, mais que vous ne pouvez peut-être pas résoudre ce problème.

    Le prix est un couplage étroit, des dépendances cachées et tout ce qui ne va pas dans l’état global. Mais vous en souffrez probablement déjà.

  4. Instanciation paresseuse.

    Ce n'est pas une partie nécessaire d'un singleton, mais il semble que le moyen le plus populaire de les implémenter. Mais, bien que l'instanciation paresseuse soit une bonne chose, vous n'avez pas vraiment besoin d'un singleton pour atteindre cet objectif.

Mise en œuvre typique

L'implémentation typique est une classe avec un constructeur privé, une variable d'instance statique et une méthode statique getInstance () avec une instanciation paresseuse.

Outre les problèmes mentionnés ci-dessus, ceci est lié au principe de responsabilité unique , car la classe contrôle sa propre instanciation et son propre cycle de vie , en plus des autres responsabilités déjà assumées par la classe.

Conclusion

Dans de nombreux cas, vous pouvez obtenir le même résultat sans singleton ni état global. Au lieu de cela, vous devez utiliser une injection de dépendance et envisager un conteneur d'injection de dépendance .

Cependant, il existe des cas d'utilisation dans lesquels il reste les exigences valides suivantes:

  • Instance unique (mais pas d'instanciation unique)
  • Accès global (parce que vous travaillez avec un cadre d'âge du bronze)
  • Instanciation paresseuse (parce que c'est bien d'avoir)

Alors, voici ce que vous pouvez faire dans ce cas:

  • Créez une classe C que vous souhaitez instancier, avec un constructeur public.

  • Créez une classe S séparée avec une variable d'instance statique et une méthode S :: getInstance () statique avec instanciation lente, qui utilisera la classe C pour l'instance.

  • Éliminez tous les effets secondaires du constructeur de C. A la place, placez ces effets secondaires dans la méthode S :: getInstance ().

  • Si vous avez plusieurs classes avec les exigences ci-dessus, vous pouvez envisager de gérer les instances de classe avec un petit conteneur de service local et d'utiliser l'instance statique uniquement pour le conteneur. Donc, S :: getContainer () vous donnera un conteneur de service instancié paresseux, et vous récupérerez les autres objets du conteneur.

  • Évitez d’appeler le getInstance () statique où vous le pouvez. Utilisez l'injection de dépendance à la place, dans la mesure du possible. En particulier, si vous utilisez l'approche conteneur avec plusieurs objets qui dépendent les uns des autres, aucun de ceux-ci ne devrait appeler S :: getContainer ().

  • Créez éventuellement une interface que la classe C implémente et utilisez-la pour documenter la valeur de retour de S :: getInstance ().

(Appelons-nous encore cela un singleton? Je laisse cela à la section des commentaires ..)

Avantages:

  • Vous pouvez créer une instance distincte de C pour les tests unitaires, sans toucher aucun état global.

  • La gestion des instances est séparée de la classe elle-même -> séparation des préoccupations, principe de responsabilité unique.

  • Il serait assez facile de laisser S :: getInstance () utiliser une classe différente pour l'instance, ou même déterminer dynamiquement quelle classe utiliser.

don Quichotte
la source
1

Personnellement, j'utiliserai des singletons quand j'ai besoin de 1, 2 ou 3, ou d'une quantité limitée d'objets pour la classe en question. Ou je veux faire comprendre à l'utilisateur de ma classe que je ne veux pas que plusieurs instances de ma classe soient créées pour que cela fonctionne correctement.

De plus, je ne l'utiliserai que si j'ai besoin de l'utiliser presque partout dans mon code et je ne veux pas passer d'objet en tant que paramètre à chaque classe ou fonction qui en a besoin.

De plus, je n'utiliserai un singleton que s'il ne casse pas la transparence référentielle d'une autre fonction. Ce qui veut dire qu’avec certaines entrées, il produira toujours la même sortie. C'est-à-dire que je ne l'utilise pas pour un état global. À moins que cet état global ne soit éventuellement initialisé une fois et ne soit jamais modifié.

Pour savoir quand ne pas l'utiliser, reportez-vous à la section 3 ci-dessus et changez-les en sens inverse.

Brian R. Bondy
la source
3
-0 mais j'ai presque -1. Je préférerais le passer partout dans mon code plutôt que d'utiliser un singleton MAIS si je suis vraiment paresseux, je peux le faire. J'utiliserais plutôt des vars globaux si je pouvais ou des membres statiques. les vars globaux sont vraiment bien préférables (à moi), alors les singletons. Les globes ne mentent pas