Il y a eu beaucoup de discussions ces derniers temps sur les problèmes liés à l'utilisation (et à la surutilisation) de singletons. J'ai aussi été l'une de ces personnes plus tôt dans ma carrière. Je peux voir quel est le problème maintenant, et pourtant, il existe encore de nombreux cas où je ne vois pas de solution de rechange intéressante - et peu de discussions anti-singleton en fournissent réellement une.
Voici un exemple réel d'un grand projet récent auquel j'ai participé:
L'application était un client lourd avec de nombreux écrans et composants distincts, qui utilisait d'énormes quantités de données provenant d'un état de serveur qui n'était pas mis à jour trop souvent. Ces données ont été essentiellement mises en cache dans un objet "manager" de Singleton - "l'état global" redouté. L’idée était d’avoir cet endroit unique dans l’application qui conserve les données stockées et synchronisées. Ainsi, tous les nouveaux écrans ouverts peuvent simplement interroger la plupart de leurs besoins à partir de là, sans faire de requêtes répétitives pour diverses données de support provenant du serveur. Demander constamment au serveur prendrait trop de bande passante - et je parle de milliers de dollars de factures Internet supplémentaires par semaine, donc c'était inacceptable.
Y a-t-il une autre approche qui pourrait être appropriée ici que d'avoir fondamentalement ce type d'objet de cache de gestionnaire de données global? Cet objet ne doit pas obligatoirement être un "Singleton", bien sûr, mais il est conceptuellement logique de l'être. Quelle est une belle alternative propre ici?
la source
Réponses:
Il est important de faire la distinction entre les instances uniques et le modèle de conception Singleton .
Les instances uniques sont simplement une réalité. La plupart des applications ne sont conçues que pour fonctionner avec une configuration à la fois, une interface utilisateur à la fois, un système de fichiers à la fois, etc. S'il y a beaucoup d'état ou de données à gérer, alors vous voudrez certainement avoir une seule instance et la garder en vie le plus longtemps possible.
Le modèle de conception Singleton est un type très spécifique d'instance unique, à savoir:
C'est à cause de ce choix de conception spécifique que le motif introduit plusieurs problèmes potentiels à long terme:
Aucun de ces symptômes n’est endémique à des cas uniques, il n’ya que le modèle Singleton.
Que pouvez-vous faire à la place? Il suffit simplement de ne pas utiliser le motif Singleton.
Citant de la question:
Ce concept a un nom, comme vous le suggérez, mais son son est incertain. Cela s'appelle une cache . Si vous voulez avoir envie, vous pouvez appeler cela un "cache hors ligne" ou simplement une copie hors ligne de données distantes.
Une cache n'a pas besoin d'être un singleton. Il se peut que vous deviez avoir une seule instance si vous souhaitez éviter d'extraire les mêmes données pour plusieurs instances de cache. mais cela ne signifie pas que vous devez réellement tout exposer à tout le monde .
La première chose que je ferais est de séparer les différentes zones fonctionnelles du cache en interfaces distinctes. Par exemple, supposons que vous fabriquiez le pire clone YouTube au monde basé sur Microsoft Access:
Vous avez ici plusieurs interfaces décrivant les types de données spécifiques auxquels une classe particulière peut avoir besoin d'accéder - médias, profils utilisateur et pages statiques (comme la page d'accueil). Tout cela est mis en œuvre par un méga-cache, mais vous concevez vos classes individuelles pour qu'elles acceptent les interfaces, de sorte qu'elles ne s'intéressent pas à leur type d'instance. Vous initialisez une fois l'instance physique au démarrage de votre programme, puis vous ne faites que commencer à transmettre les instances (transtypées vers un type d'interface particulier) via des constructeurs et des propriétés publiques.
C'est ce qu'on appelle l' injection de dépendance , au fait; vous n'avez pas besoin d'utiliser Spring ni aucun conteneur IoC spécial, uniquement si votre conception de classe générale accepte ses dépendances de l'appelant au lieu de les instancier de son propre chef ou de faire référence à un état global .
Pourquoi devriez-vous utiliser la conception basée sur une interface? Trois raisons:
Cela rend le code plus facile à lire; vous pouvez clairement comprendre depuis les interfaces les données sur lesquelles dépendent les classes dépendantes.
Si et quand vous réalisez que Microsoft Access n'est pas le meilleur choix pour un back-end de données, vous pouvez le remplacer par quelque chose de mieux - disons SQL Server.
Si et quand vous réalisez que SQL Server n'est pas le meilleur choix pour un média en particulier , vous pouvez diviser votre implémentation sans affecter aucune autre partie du système . C’est là que le vrai pouvoir de l’abstraction entre en jeu.
Si vous voulez aller plus loin, vous pouvez utiliser un conteneur IoC (infrastructure DI) tel que Spring (Java) ou Unity (.NET). Presque chaque infrastructure d’ID réalisera sa propre gestion de la durée de vie et vous permettra notamment de définir un service particulier comme une instance unique (l’appelant souvent "singleton", mais ce n’est que par familiarité). Fondamentalement, ces frameworks vous épargnent la plupart des tâches fastidieuses consistant à passer manuellement des instances, mais ils ne sont pas strictement nécessaires. Vous n'avez pas besoin d'outils spéciaux pour mettre en œuvre cette conception.
Par souci d’exhaustivité, je dois souligner que la conception ci-dessus n’est vraiment pas idéale non plus. Lorsque vous traitez avec un cache (comme vous êtes), vous devriez en fait avoir un calque entièrement séparé . En d'autres termes, un design comme celui-ci:
L'avantage de ceci est que vous n'avez même jamais besoin de casser votre
Cache
instance si vous décidez de refactoriser; vous pouvez changer la façon dont le média est stocké en lui fournissant simplement une autre implémentation deIMediaRepository
. Si vous réfléchissez à la manière dont cela s’assemble, vous constaterez qu’il ne crée toujours qu’une seule instance physique d’un cache. Vous n’avez donc jamais besoin d’extraire deux fois les mêmes données.Rien de tout cela ne signifie que chaque logiciel dans le monde doit être conçu selon ces normes rigoureuses de cohésion élevée et de couplage lâche; cela dépend de la taille et de la portée du projet, de votre équipe, de votre budget, des délais, etc. Mais si vous demandez quel est le meilleur design (à utiliser à la place d'un singleton), alors c'est là.
PS Comme d'autres l'ont dit, ce n'est probablement pas la meilleure idée pour les classes dépendantes de savoir qu'elles utilisent un cache - c'est un détail d'implémentation dont elles ne devraient tout simplement pas se soucier. Cela étant dit, l'architecture globale ressemblerait toujours beaucoup à ce qui est décrit ci-dessus, vous ne voudriez tout simplement pas que les interfaces individuelles soient appelées Caches . Au lieu de cela, vous les nommeriez Services ou quelque chose de similaire.
la source
Dans le cas que vous donnez, il semble que l'utilisation d'un Singleton ne soit pas le problème, mais le symptôme d'un problème - un problème architectural plus vaste.
Pourquoi les écrans interrogent-ils l'objet cache pour obtenir des données? La mise en cache doit être transparente pour le client. Il doit exister une abstraction appropriée pour la fourniture des données, et la mise en œuvre de cette abstraction peut utiliser la mise en cache.
Le problème est probablement que les dépendances entre les parties du système ne sont pas configurées correctement, ce qui est probablement systémique.
Pourquoi les écrans doivent-ils savoir où ils obtiennent leurs données? Pourquoi les écrans ne sont-ils pas fournis avec un objet capable de répondre à leurs demandes de données (derrière lequel un cache est caché)? Souvent, la responsabilité de la création d’écrans n’est pas centralisée et il n’ya donc aucun intérêt à injecter des dépendances.
Encore une fois, nous examinons des problèmes d’architecture et de conception à grande échelle.
En outre, il est très important de comprendre que la durée de vie d'un objet peut être complètement dissociée de la manière dont il est trouvé pour utilisation.
Un cache devra rester en vie tout au long de la vie de l'application (pour être utile), donc la durée de vie de cet objet est celle d'un Singleton.
Mais le problème avec Singleton (du moins l’implémentation commune de Singleton en tant que classe / propriété statique), est de savoir comment les autres classes qui l’utilisent s’y prennent pour le trouver.
Avec une implémentation Singleton statique, la convention consiste simplement à l’utiliser chaque fois que nécessaire. Mais cela cache complètement la dépendance et couple étroitement les deux classes.
Si nous fournissons la dépendance à la classe, cette dépendance est explicite et tout ce que la classe consommatrice doit connaître est le contrat disponible pour pouvoir être utilisé.
la source
J'ai écrit un chapitre entier sur cette question. Principalement dans le contexte des jeux, mais la plupart d’entre eux devraient s’appliquer en dehors des jeux.
tl; dr:
Le modèle Gang of Four Singleton a deux objectifs: vous donner un accès pratique à un objet de n'importe où et vous assurer qu'une seule instance peut être créée. 99% du temps, tout ce qui vous intéresse est la première moitié de cela, et le fait de rouler la seconde moitié pour l'obtenir ajoute une limitation inutile.
De plus, il existe de meilleures solutions pour un accès pratique. Rendre un objet global est l’option nucléaire pour résoudre ce problème et facilite la destruction de votre encapsulation. Tout ce qui ne va pas avec les globals s'applique complètement aux singletons.
Si vous l'utilisez simplement parce que vous avez beaucoup d'endroits dans le code qui doivent toucher le même objet, essayez de trouver un meilleur moyen de le donner uniquement à ces objets sans l'exposer à la totalité de la base de code. Autres solutions:
Fossé entièrement. J'ai vu beaucoup de classes de singleton qui n'ont pas d'état et sont juste des sacs de fonctions d'assistance. Ceux-ci n'ont pas besoin d'une instance du tout. Il suffit de les transformer en fonctions statiques ou de les déplacer dans l’une des classes que la fonction prend en argument. Vous n'auriez pas besoin d'une
Math
classe spéciale si vous pouviez simplement faire123.Abs()
.Passez-le. La solution simple si une méthode a besoin d'un autre objet est simplement de la transmettre. Il n'y a rien de mal à transmettre certains objets.
Mettez-le dans la classe de base. Si vous avez beaucoup de classes qui ont toutes besoin d'accéder à un objet spécial et qui partagent une classe de base, vous pouvez faire de cet objet un membre de la base. Lorsque vous le construisez, passez l'objet. Maintenant, les objets dérivés peuvent tous l'obtenir quand ils en ont besoin. Si vous le protégez, vous vous assurez que l'objet reste toujours encapsulé.
la source
Ce n'est pas l'état global en soi, c'est le problème.
Vraiment vous devez seulement vous inquiéter
global mutable state
. L'état constant n'est pas affecté par les effets secondaires et pose donc moins de problèmes.Le problème majeur avec singleton est qu’il ajoute un couplage et rend donc les choses plus difficiles, comme les tests. Vous pouvez réduire le couplage en récupérant le singleton d'une autre source (par exemple une usine). Cela vous permettra de découpler le code d'une instance particulière (bien que vous deveniez plus couplé à l'usine (mais au moins l'usine peut avoir d'autres implémentations pour différentes phases)).
Dans votre situation, je pense que vous pouvez vous en tirer à condition que votre singleton implémente réellement une interface (de sorte qu'une alternative puisse être utilisée dans d'autres situations).
Mais un autre inconvénient majeur des singletons est qu’une fois qu’ils sont en place, les supprimer du code et les remplacer par quelque chose d’autre devient une tâche vraiment ardue (il ya à nouveau ce couplage).
la source
le weekend
oops, c'est l'un des nôtres). Merci :-)Alors quoi? Puisque personne ne l'a dit: Boîte à outils . C'est si vous voulez des variables globales .
Mais devinez quoi? C'est un singleton!
Et qu'est-ce qu'un singleton?
Peut-être que c'est là que la confusion commence.
Pour moi, le singleton est un objet contraint de n'avoir qu'une seule instance et toujours. Vous pouvez y accéder n'importe où, à tout moment, sans avoir à l'instancier. C'est pourquoi il est si étroitement lié à
static
. À titre de comparaison,static
est fondamentalement la même chose, sauf que ce n'est pas une instance. Nous n'avons pas besoin de l'instancier, nous ne pouvons même pas, car c'est alloué automatiquement. Et cela peut et apporte des problèmes.D'après mon expérience, le simple remplacement
static
de Singleton a résolu de nombreux problèmes dans le cadre d'un projet de sac patchwork de taille moyenne. Cela signifie seulement qu'il a une certaine utilité pour les projets mal conçus. Je pense qu'il y a trop de discussion si le motif singleton est utile ou non et je ne peux pas vraiment discuter si c'est vraiment mauvais . Mais il existe toujours de bons arguments en faveur du singleton par rapport aux méthodes statiques, en général .La seule chose dont je suis sûr qu’il est mauvais en ce qui concerne les singletons, c’est de les utiliser en ignorant les bonnes pratiques. C'est en effet quelque chose de pas facile à gérer. Mais les mauvaises pratiques peuvent être appliquées à n'importe quel modèle. Et, je sais, c'est trop générique pour dire que ... je veux dire, c'est trop.
Ne vous méprenez pas!
En termes simples, tout comme les vars globaux , les singletons doivent toujours être évités . Spécialement parce qu'ils sont trop maltraités. Mais les serveurs mondiaux ne peuvent pas toujours être évités et nous devrions les utiliser dans ce dernier cas.
Quoi qu'il en soit, il existe de nombreuses suggestions autres que la Boîte à outils et, tout comme la boîte à outils, chacune a son application ...
Autres alternatives
Le meilleur article que je viens de lire sur les singletons suggère le localisateur de services comme alternative. Pour moi, il s’agit d’une " boîte à outils statique ", si vous voulez. En d'autres termes, faites du service de localisation un singleton et vous aurez une boîte à outils. Cela va à l’encontre de sa suggestion initiale d’éviter le singleton, bien sûr, mais ce n’est que pour faire respecter le problème du singleton, c’est de savoir comment il est utilisé, pas le motif en soi.
D'autres suggèrent le modèle d'usine comme alternative. C'était la première alternative d'un collègue et nous l'avons rapidement éliminée pour notre utilisation en tant que var global . Il a son usage, mais les singletons aussi.
Les deux alternatives ci-dessus sont de bonnes alternatives. Mais tout dépend de votre utilisation.
Maintenant, impliquer les singletons doit être évité à tout prix, c'est faux
Les inaptitudes (à l’abstrait ou à la sous-classe) sont bien là, mais alors quoi? Ce n'est pas fait pour ça. Il n'y a pas d' incapacité d'interface , pour autant que je sache . Un couplage élevé peut également être là, mais c'est simplement parce qu'il est couramment utilisé. Ce n'est pas obligé . En fait, le couplage en soi n'a rien à voir avec le motif singleton. Ceci étant clarifié, cela élimine également déjà la difficulté à tester. Quant à la difficulté de paralléliser, cela dépend de la langue et de la plate-forme, donc encore une fois, ce n’est pas un problème pour le modèle.
Exemples pratiques
Je vois souvent que 2 sont utilisés, à la fois en faveur et contre des singletons. Cache Web (mon cas) et service de journalisation .
L'exploitation forestière, diront certains , est un exemple singleton parfait, car, et je cite:
Tandis que d’autres diront qu’il est difficile d’élargir le service de journalisation une fois que vous réalisez qu’il ne devrait en réalité pas y avoir une seule instance.
Eh bien, je dis les deux arguments sont valables. Ici encore, le problème ne concerne pas le motif singleton. C'est sur les décisions architecturales et la pondération si la refactorisation est un risque viable. C'est un problème supplémentaire lorsque, habituellement, la refactorisation est la dernière mesure corrective nécessaire.
la source
java.awt.Toolkit
. Mon point est cependant le même: celaToolbox
ressemble à une corbeille de morceaux sans rapport, plutôt qu’à une classe cohérente ayant un seul but. Cela ne me semble pas être un bon design. (Notez que l'article auquel vous faites référence est de 2001, avant que l'injection de dépendance et les conteneurs DI ne soient devenus monnaie courante.)Mon principal problème avec le modèle de conception singleton est qu’il est très difficile d’écrire de bons tests unitaires pour votre application.
Chaque composant dépendant de ce "manager" le fait en interrogeant son instance singleton. Et si vous voulez écrire un test unitaire pour un tel composant, vous devez injecter des données dans cette instance de singleton, ce qui risque de ne pas être simple.
Si par contre votre "manager" est injecté dans les composants dépendants via un paramètre constructeur et que le composant ne connaisse pas le type concret du manager, seulement une interface ou une classe de base abstraite implémentée par le manager, alors une unité test pourrait fournir d'autres implémentations du gestionnaire lors du test des dépendances.
Si vous utilisez des conteneurs IOC pour configurer et instancier les composants de votre application, vous pouvez facilement configurer votre conteneur IOC de manière à ne créer qu'une seule instance du "gestionnaire", ce qui vous permet de réaliser la même chose, une seule instance contrôlant le cache d'applications global. .
Mais si vous ne vous souciez pas des tests unitaires, un modèle de conception singleton peut parfaitement convenir. (mais je ne le ferais pas quand même)
la source
Un singleton n’est pas fondamentalement mauvais en ce sens que tout ce que l’informatique de conception peut être bon ou mauvais. Cela ne peut être que correct (donne les résultats escomptés) ou non. Cela peut également être utile ou non, si cela rend le code plus clair ou plus efficace.
Un cas dans lequel les singletons sont utiles est quand ils représentent une entité qui est vraiment unique. Dans la plupart des environnements, les bases de données sont uniques, il n'y en a qu'une. La connexion à cette base de données peut s'avérer compliquée car elle nécessite des autorisations spéciales ou une traversée de plusieurs types de connexion. Organiser cette connexion en un singleton a probablement beaucoup de sens pour cette seule raison.
Mais vous devez également vous assurer que le singleton est vraiment un singleton, et non une variable globale. Ceci est important lorsque la base de données unique est constituée de 4 bases de données, une pour la production, la mise en place, le développement et les montages de test. Un Singleton de base de données déterminera à qui il doit se connecter, saisira l'instance unique de cette base de données, la connectera si nécessaire et le renverra à l'appelant.
Lorsqu'un singleton n'est pas vraiment un singleton (c'est alors que la plupart des programmeurs s'énervent), c'est un global instancié paresseusement, il n'y a aucune possibilité d'injecter une instance correcte.
Une autre caractéristique utile d'un motif singleton bien conçu est qu'il n'est souvent pas observable. L'appelant demande une connexion. Le service qui le fournit peut renvoyer un objet en pool ou, s'il effectue un test, il peut en créer un pour chaque appelant ou fournir un objet fictif à la place.
la source
L'utilisation du motif singleton qui représente des objets réels est parfaitement acceptable. J'écris pour l'iPhone, et il y a beaucoup de singletons dans le framework Cocoa Touch. L'application elle-même est représentée par un singleton de la classe
UIApplication
. Vous êtes une seule application, il est donc approprié de la représenter avec un singleton.L'utilisation d'un singleton en tant que classe de gestionnaire de données est acceptable tant qu'elle est conçue correctement. S'il s'agit d'un ensemble de propriétés de données, ce n'est pas mieux que la portée globale. S'il s'agit d'un ensemble de getters et de setters, c'est mieux, mais ce n'est toujours pas génial. S'il s'agit d'une classe qui gère réellement toutes les interfaces de données, y compris peut-être l'extraction de données distantes, la mise en cache, la configuration et le démontage ... Cela pourrait être très utile.
la source
Les singletons ne sont que la projection d'une architecture orientée service dans un programme.
Une API est un exemple de singleton à un niveau de protocole. Vous accédez à Twitter, Google, etc. via essentiellement des singletons. Alors, pourquoi les singletons deviennent-ils mauvais dans un programme?
Cela dépend de la façon dont vous pensez d'un programme. Si vous considérez un programme comme une société de services plutôt que comme des instances en cache liées au hasard, les singletons sont parfaitement logiques.
Les singletons sont un point d'accès au service. L'interface publique vers une bibliothèque de fonctionnalités étroitement liée qui cache peut-être une architecture interne très sophistiquée.
Donc, je ne vois pas un singleton différent de celui d’une usine. Le singleton peut avoir des paramètres de constructeur passés. Il peut être créé par un contexte qui sait comment résoudre l'imprimante par défaut par rapport à tous les mécanismes de sélection possibles, par exemple. Pour tester, vous pouvez insérer votre propre maquette. Donc, cela peut être assez flexible.
La clé se trouve en interne dans un programme lorsque j'exécute et nécessite un peu de fonctionnalité. Je peux accéder au singleton en toute confiance sur le fait que le service est opérationnel et prêt à être utilisé. Cela est essentiel lorsque différents threads démarrant dans un processus doivent passer par une machine à états pour être considérés comme prêts.
En règle générale, je voudrais envelopper une
XxxService
classe qui enveloppe un singleton autour de la classeXxx
. Le singleton est pas en classeXxx
du tout, il est séparé dans une autre classe,XxxService
. En effet,Xxx
plusieurs instances peuvent être utilisées, bien que cela ne soit pas probable, mais nous souhaitons toujours avoir uneXxx
instance accessible de manière globale sur chaque système.XxxService
fournit une belle séparation des préoccupations.Xxx
Il n'est pas nécessaire d'appliquer une stratégie de singleton, mais nous pouvons l'utiliserXxx
comme un singleton lorsque nous en avons besoin.Quelque chose comme:
la source
Première question, trouvez-vous beaucoup de bugs dans l'application? peut-être oublier de mettre à jour le cache, ou un mauvais cache ou avoir du mal à changer? (Je me souviens d'une application ne changerait pas de taille sauf si vous changiez aussi de couleur ... vous pouvez toutefois changer la couleur et conserver la taille).
Ce que vous feriez, c'est d'avoir cette classe mais SUPPRIMER TOUS LES MEMBRES STATIQUES. Ok ce n'est pas nessacary mais je le recommande. Vraiment, vous venez d’initialiser la classe comme une classe normale et de PASSER le pointeur. Ne parlez pas de frigen, dites ClassIWant.APtr (). LetMeChange.ANYTHINGATALL (). Andhave_no_structure ()
C'est plus de travail mais vraiment, c'est moins déroutant. Certains endroits où vous ne devriez pas changer des choses que vous ne pouvez pas maintenant car ce n’est plus global. Toutes mes classes de manager sont des classes régulières, traitez-le simplement comme ça.
la source
OMI, votre exemple semble bien. Je suggérerais de factoriser comme suit: objet de cache pour chaque objet de données (et derrière chaque); Les objets en cache et les objets d'accès à la base de données ont la même interface. Cela donne la possibilité d'échanger des caches dans et hors du code; De plus, cela donne une voie d'expansion facile.
Graphique:
L'accesseur de base de données et le cache peuvent hériter du même objet ou du même type de canard pour ressembler au même objet, peu importe. Tant que vous pouvez brancher / compiler / tester et que cela fonctionne toujours.
Cela permet de découpler les éléments afin que vous puissiez ajouter de nouveaux caches sans avoir à entrer et modifier un objet Uber-Cache. YMMV. IANAL. ETC.
la source
Un peu tard pour la fête, mais quand même.
Singleton est un outil dans une boîte à outils, comme n'importe quoi d'autre. J'espère que vous avez plus dans votre boîte à outils qu'un simple marteau.
Considère ceci:
contre
1er cas conduit à un couplage élevé, etc. La 2ème manière n'a pas de problèmes @Aaronaught décrit, pour autant que je sache. Son tout sur la façon dont vous l'utilisez.
la source
Demandez à chaque écran de prendre le gestionnaire dans leur constructeur.
Lorsque vous démarrez votre application, vous créez une instance du gestionnaire et vous la transmettez.
Cette opération s'appelle Inversion of Control et vous permet d'échanger le contrôleur lorsque la configuration change et lors des tests. De plus, vous pouvez exécuter plusieurs instances de votre application ou des parties de votre application en parallèle (utile pour les tests!). Enfin, votre responsable mourra avec son objet propriétaire (la classe de démarrage).
Alors, structurez votre application comme un arbre, où tout ce qui se trouve au-dessus d’eux est utilisé en dessous d’eux. N'implémentez pas une application comme un maillage, où tout le monde se connaît et se trouve grâce à des méthodes globales.
la source