Quand le modèle Singleton ne doit-il pas être utilisé? (Outre l’évidence)

Je sais bien que vous souhaitez utiliser Singleton pour fournir un point d’access global à un état ou à un service. Les avantages du modèle Singleton n’ont pas besoin d’être énumérés dans cette question.

Ce qui m’intéresse, ce sont les situations où Singleton peut sembler être un bon choix au début, mais pourrait revenir pour vous mordre. À maintes resockets, j’ai vu des auteurs dans des livres et des affiches sur SO dire que le modèle Singleton est souvent une très mauvaise idée.

The Gang of Four affirme que vous voudrez utiliser Singleton lorsque:

  • il doit exister exactement une instance d’une classe et celle-ci doit être accessible aux clients à partir d’un point d’access connu.
  • lorsque la seule instance doit être extensible par sous-classement, et que les clients doivent pouvoir utiliser une instance étendue sans modifier leur code.

Ces points, certes notables, ne sont pas les plus pratiques que je recherche.

Quelqu’un at-il un ensemble de règles ou de mises en garde que vous utilisez pour évaluer si vous êtes vraiment sûr de vouloir utiliser un Singleton?

Version récapitulative:

Vous savez à quelle fréquence vous utilisez des globales? Ok, maintenant utilisez Singletons EVEN LESS. Beaucoup moins en fait. Presque jamais. Ils partagent tous les problèmes que les globaux rencontrent avec le couplage caché (impact direct sur la testabilité et la maintenabilité), et souvent la ressortingction “unique peut exister” est en réalité une hypothèse erronée.

Réponse détaillée:

La chose la plus importante à réaliser à propos d’un singleton est qu’il s’agit d’un état global. C’est un modèle pour exposer une seule instance d’access globalement non atténuée . Cela a tous les problèmes de la programmation que les globaux ont, mais adopte également de nouveaux détails intéressants sur l’implémentation et très peu de valeur réelle (ou, en fait, cela peut entraîner un coût supplémentaire inutile avec l’aspect d’instance unique). L’implémentation est suffisamment différente pour que les gens le prennent souvent pour une méthode d’encapsulation orientée object quand il ne s’agit que d’une simple instance globale simple.

La seule situation dans laquelle vous devriez considérer un singleton est que le fait d’avoir plusieurs instances de données déjà globales serait en réalité une erreur d’access logique ou matériel. Même dans ce cas, vous ne devriez pas traiter directement le singleton, mais plutôt fournir une interface wrapper qui peut être instanciée autant de fois que vous en avez besoin, mais accède uniquement à l’état global. De cette manière, vous pouvez continuer à utiliser l’ dependency injections et, si vous pouvez unifier l’état global du comportement de la classe, il ne s’agit pas d’un changement radical dans votre système.

Il y a des problèmes subtils avec cela, cependant, quand il semble que vous ne dépendez pas des données globales, mais vous êtes. De sorte que (en utilisant l’ injection de dépendance de l’interface qui enveloppe le singleton) n’est qu’une suggestion et non une règle. En général, c’est encore mieux car au moins vous pouvez voir que la classe repose sur le singleton alors que l’utilisation de la fonction :: instance () dans le ventre d’une fonction membre de la classe masque cette dépendance. Cela vous permet également d’extraire des classes en vous basant sur l’état global et de faire de meilleurs tests unitaires , et vous pouvez passer des objects fictifs où si vous vous fiez directement au singleton dans la classe, c’est BEAUCOUP plus difficile.

Lorsque vous préparez un appel singleton :: instance qui s’installe également dans une classe, vous rendrez l’ inheritance impossible . Les contournements interrompent généralement la partie “instance unique” d’un singleton. Envisagez une situation dans laquelle vous avez plusieurs projets reposant sur du code partagé dans une classe NetworkManager. Même si vous voulez que ce NetworkManager soit un état global et une instance unique, vous devriez être très sceptique quant à son intégration dans un singleton. En créant un singleton simple qui s’installe automatiquement, il est impossible pour un autre projet de dériver de cette classe.

Beaucoup considèrent le ServiceLocator comme un anti-pattern, mais je pense qu’il est un demi-meilleur que le Singleton et éclipse efficacement l’objective du pattern Go4. Il existe de nombreuses manières d’implémenter un localisateur de service, mais le concept de base est de diviser la construction de l’object et l’access de l’object en deux étapes. De cette manière, lors de l’exécution, vous pouvez connecter le service dérivé approprié, puis y accéder à partir d’un seul sharepoint contact global. Cela présente l’avantage d’un ordre de construction d’object explicite et vous permet également de dériver de votre object de base. Ceci est toujours mauvais pour la plupart des raisons indiquées, mais il est moins mauvais que le Singleton et est un remplacement instantané.

Un exemple spécifique d’un singleton acceptable (read: servicelocator) peut consister à encapsuler une interface de style c à instance unique comme SDL_mixer. Un exemple de singleton implémenté souvent naïvement où il ne devrait probablement pas l’être est dans une classe de journalisation (que se passe-t-il lorsque vous souhaitez vous connecter à la console ET au disque? Ou si vous souhaitez consigner les sous-systèmes séparément).

Les problèmes les plus importants liés à l’état global, cependant, se posent presque toujours lorsque vous essayez d’implémenter des tests unitaires appropriés (et vous devriez essayer de le faire). Il devient tellement difficile de gérer votre application lorsque les classes auxquelles vous n’avez pas access tentent d’écrire et de lire des fichiers sans ressortingction, de vous connecter à des serveurs en direct et d’envoyer des données réelles, ou de diffuser du son sur vos haut-parleurs. bon gré mal gré. C’est beaucoup, BEAUCOUP, préférable d’utiliser l’dependency injections pour que vous puissiez simuler une classe à ne rien faire (et voir que vous devez le faire dans le constructeur de la classe) dans le cas d’un plan de test et l’état global dont dépend votre classe

Liens connexes:

  • La brutalité invoquée par Global State et Singletons
  • Injection de dépendance pour éviter les singletons
  • Usines et Singletons

Utilisation du patron vs émergence

Les modèles sont utiles en tant qu’idées et termes, mais malheureusement, les gens semblent ressentir le besoin «d’utiliser» un modèle lorsque les modèles sont réellement mis en œuvre en fonction des besoins. Souvent, le singleton est spécialement lié, car il s’agit d’un modèle communément discuté. Concevez votre système avec une conscience des modèles, mais ne concevez pas votre système spécifiquement pour les plier juste parce qu’ils existent. Ce sont des outils conceptuels utiles, mais comme vous n’utilisez pas tous les outils de la boîte à outils simplement parce que vous le pouvez, vous ne devriez pas faire la même chose avec les modèles. Utilisez-les au besoin et pas plus ou moins.

Exemple de localisateur de service à instance unique

#include  #include  class Service { public: static Service* Instance(){ return _instance; } static Service* Connect(){ assert(_instance == nullptr); _instance = new Service(); } virtual ~Service(){} int GetData() const{ return i; } protected: Service(){} static Service* _instance; int i = 0; }; class ServiceDerived : public Service { public: static ServiceDerived* Instance(){ return dynamic_cast(_instance); } static ServiceDerived* Connect(){ assert(_instance == nullptr); _instance = new ServiceDerived(); } protected: ServiceDerived(){i = 10;} }; Service* Service::_instance = nullptr; int main() { //Swap which is Connected to test it out. Service::Connect(); //ServiceDerived::Connect(); std::cout << Service::Instance()->GetData() << "\n" << ((ServiceDerived::Instance())? ServiceDerived::Instance()->GetData() :-1); return 0; } 

Un mot: test

L’une des caractéristiques de la testabilité est un couplage lâche des classes, vous permettant d’isoler une seule classe et de la tester complètement. Lorsqu’une classe utilise un singleton (et que je parle d’un singleton classique, qui applique sa propre singularité à la méthode statique getInstance ()), l’utilisateur singleton et le singleton deviennent inexsortingcablement liés. Il n’est plus possible de tester l’utilisateur sans tester le singleton.

Les singletons sont un désastre à tester. Comme ils sont statiques, vous ne pouvez pas les supprimer avec une sous-classe. Comme ils sont globaux, vous ne pouvez pas facilement modifier la référence vers laquelle ils sont dirigés sans recompilation ou sous une lourde charge. Tout ce qui utilise le singleton obtient comme par magie une référence globale à quelque chose de difficile à contrôler. Cela rend difficile la limitation de la scope d’un test.

Les erreurs les plus importantes avec Singleton que vous avez vues sont que vous concevez un système mono-utilisateur (par exemple, un programme de bureau) et que vous utilisez Singleton pour beaucoup de choses (par exemple, les parameters) et que vous voulez ou un service.

Il est similaire à ce qui est arrivé aux fonctions C avec des tampons statiques internes lorsqu’elles ont été utilisées dans des programmes multithread.

Je dirais éviter les singletons à tout prix. Cela limite la mise à l’échelle des applications. Analysez véritablement le problème que vous rencontrez et réfléchissez à l’évolutivité et prenez des décisions en fonction de votre évolutivité.

En fin de compte, un singleton agit comme un goulot d’étranglement si sa conception est incorrecte.

Parfois, vous introduisez ce goulot d’étranglement sans bien comprendre les implications que cela aura sur votre application.

J’ai rencontré des problèmes lorsqu’il s’agissait d’applications multithread qui tentent d’accéder à une ressource singleton, mais qui se heurtent à des blocages. C’est pourquoi j’essaie d’éviter un singleton autant que possible.

Si vous introduisez des singletons dans votre conception, assurez-vous de comprendre les implications de l’exécution, faites des diagrammes et déterminez où cela pourrait causer un problème.

Singleton est souvent utilisé comme un attrape-tout pour des choses que les gens ne peuvent pas être obligés d’encapsuler correctement à l’endroit où ils en ont réellement besoin, avec des accesseurs appropriés.

Le résultat final est une archive qui rassemble éventuellement tous les éléments static du système entier. Combien de personnes ici n’ont JAMAIS vu une classe appelée Globals dans un code soi-disant OOD avec Globals elles devaient travailler? Pouah.

Je ne l’éviterais pas complètement dans aucun design. Cependant, il faut faire attention à son utilisation. Il peut devenir l’object de Dieu dans de nombreux scénarios et, par conséquent, vaincre l’objective.

Gardez à l’esprit, ce modèle de conception est une solution pour résoudre certains problèmes, mais pas tous les problèmes. En fait, il en va de même pour tous les modèles de conception.

Je ne me considère pas comme un programmeur expérimenté, mais mon opinion actuelle est que vous n’avez en fait pas besoin du Singleton … oui, il semble plus facile de travailler au début (comme pour les globales), mais ensuite vient le “oh my “moment où on a besoin d’une autre instance.

Vous pouvez toujours passer ou injecter l’instance, je ne vois pas vraiment de situation où il serait beaucoup plus facile ou nécessaire d’utiliser Singleton

Même si nous rejetons tout, il rest la question de la testabilité du code

Voir la première réponse dans la discussion ” Alternative à Singleton “.