it-swarm.dev

Quand Singleton est-il approprié?

Certains soutiennent que le Singleton Pattern est toujours un anti-modèle. Qu'est-ce que tu penses?

67
Fishtoaster

Les deux principales critiques des Singletons se répartissent en deux camps de ce que j'ai observé:

  1. Les singletons sont mal utilisés et abusés par des programmeurs moins compétents et donc tout devient un singleton et vous voyez du code jonché de références Class :: get_instance (). De manière générale, il n'y a qu'une ou deux ressources (comme une connexion à une base de données par exemple) qui peuvent être utilisées pour 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 tangibles lorsque vous essayez de faire des tests unitaires car elles représentent des impasses dans votre code qui ne peuvent pas être moquées ou tronquées. 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.

À la suite de ces deux, une approche courante consiste à utiliser créer un objet conteneur large pour contenir une seule instance de ces classes et seul l'objet conteneur modifie ces types de classes tandis que de nombreuses autres classes peuvent être autorisées à utiliser à partir de l'objet conteneur.

32
Noah Goodrich

Je suis d'accord 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 à d'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 n'a pas d'effets secondaires. Cependant, en silence, il communique avec une sorte d'objet singleton public, disponible dans le monde entier, de sorte que chaque fois que vous instanciez la classe, il contient des données différentes. C'est un gros problème, non seulement pour les utilisateurs de votre API, mais aussi pour la testabilité du code. Pour tester correctement le code, vous devez microgérer et connaître l'état global du singleton pour obtenir des résultats de test cohérents.

42
Magnus Wolffelt

Le modèle Singleton n'est fondamentalement qu'une variable globale initialisée paresseusement. Les variables globales sont généralement et à juste titre considérées comme mauvaises parce qu'elles permettent une action effrayante à distance entre des parties apparemment sans rapport d'un programme. Cependant, à mon humble avis, il n'y a rien de mal avec les variables globales qui sont définies une fois, à partir d'un seul endroit, dans le cadre de la routine d'initialisation d'un programme (par exemple, en lisant un fichier de configuration ou des arguments de ligne de commande) et traitées comme des constantes par la suite. Une telle utilisation des variables globales n'est différente qu'en lettre, et non en esprit, d'avoir une constante nommée déclarée au moment de la compilation.

De même, mon opinion sur Singletons est qu'ils sont mauvais si et seulement s'ils sont utilisés pour passer un état mutable entre des parties apparemment sans rapport avec 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 n'aient pas à le savoir même dans un environnement multithread, alors il n'y a rien de mal à eux.

34
dsimcha

Pourquoi les gens l'utilisent-ils?

J'ai vu un certain nombre de singletons dans le monde PHP. Je ne me souviens d'aucun cas d'utilisation où j'ai trouvé le motif justifié. Mais je pense avoir eu une idée de la motivation pourquoi les gens l'ont utilisé.

  1. Unique instance.

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

    Il s'agit d'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 db, cela signifie simplement que vous travaillez généralement avec la connexion par défaut.

  2. Unique instanciation.

    "Ne laissez pas la classe C être instanciée plus d'une fois (par processus, par demande, etc.)."

    Cela 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 repensant le composant - par ex. en éliminant les effets secondaires du constructeur de classe. Ou ils peuvent être résolus d'autres manières. Mais il pourrait toujours y avoir des cas d'utilisation légitimes.

    Vous devez également vous demander si l'exigence "un seul" signifie vraiment "un par processus". Par exemple. pour la simultanéité des ressources, l'exigence est plutôt "une par système entier, entre les processus" et non "une par processus". Et pour d'autres choses, c'est plutôt par "contexte d'application", et il se trouve que vous n'avez qu'un seul contexte d'application par processus.

    Sinon, il n'est pas nécessaire d'appliquer cette hypothèse. L'application de 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 ne disposez pas d'une infrastructure appropriée pour faire passer les objets à l'endroit où ils sont utilisés. Cela pourrait signifier que votre cadre ou votre environnement est nul, mais il n'est peut-être pas dans vos pouvoirs de résoudre ce problème.

    Le prix est un couplage étroit, des dépendances cachées et tout ce qui est mauvais à propos de l'état mondial. Mais vous en souffrez probablement déjà.

  4. Instanciation paresseuse.

    Ce n'est pas une partie nécessaire d'un singleton, mais cela semble la façon la 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 y parvenir.

Implémentation typique

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

En plus des problèmes mentionnés ci-dessus, cela mord avec le principe de responsabilité unique , car la classe le fait contrôle sa propre instanciation et cycle de vie , en plus des autres responsabilités que le classe a déjà.

Conclusion

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

Cependant, il existe des cas d'utilisation où il vous reste les exigences valides suivantes:

  • Instance unique (mais pas instanciation unique)
  • Accès global (parce que vous travaillez avec un cadre de l'âge du bronze)
  • Instanciation paresseuse (car il est juste agréable d'avoir)

Voici donc 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 distincte avec une variable d'instance statique et une méthode S :: getInstance () statique avec instanciation paresseuse, qui utilisera la classe C pour l'instance.

  • Éliminez tous les effets secondaires du constructeur de C. Au lieu de cela, 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 obtenez les autres objets du conteneur.

  • Évitez d'appeler la getInstance () statique où vous le pouvez. Utilisez plutôt l'injection de dépendance, 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 avoir à 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 toujours 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 de déterminer dynamiquement quelle classe utiliser.

7
donquixote

Personnellement, j'utiliserai des singletons lorsque j'aurai besoin de 1, 2 ou 3, ou d'une quantité limitée d'objets pour la classe particulière en question. Ou je veux dire à l'utilisateur de ma classe que je ne veux pas que plusieurs instances de ma classe soient créées pour qu'elle fonctionne correctement.

De plus, je ne l'utilise que lorsque je dois l'utiliser presque partout dans mon code et que je ne veux pas passer un 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. Cela signifie que, étant donné certaines entrées, il produira toujours la même sortie. C'est à dire. Je ne l'utilise pas pour un état global. Sauf si cet état global est initialisé une seule fois et n'a jamais changé.

Quant au moment de ne pas l'utiliser, voir les 3 ci-dessus et les changer à l'opposé.

1
Brian R. Bondy