Certains soutiennent que le motif Singleton est toujours un anti-motif. Qu'est-ce que tu penses?
Certains soutiennent que le motif Singleton est toujours un anti-motif. Qu'est-ce que tu penses?
Réponses:
Les deux principales critiques des singletons se divisent en deux camps d'après ce que j'ai observé:
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.
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.
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.
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.
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.
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.
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à.
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.
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.
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:
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.
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.