web-dev-qa-db-fra.com

Alors les singletons sont mauvais, alors quoi?

Il y a eu beaucoup de discussions ces derniers temps sur les problèmes d'utilisation (et de surutilisation) des singletons. J'ai également é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 y a encore de nombreux cas où je ne vois pas d'alternative agréable - et peu de discussions anti-Singleton en fournissent vraiment une.

Voici un exemple réel d'un projet récent majeur auquel j'ai participé:

L'application était un client lourd avec de nombreux écrans et composants séparés qui utilise d'énormes quantités de données provenant d'un état de serveur qui n'est pas mis à jour trop souvent. Ces données ont été essentiellement mises en cache dans un objet "gestionnaire" Singleton - le redouté "état global". L'idée était d'avoir cet emplacement unique dans l'application qui conserve les données stockées et synchronisées, puis tous les nouveaux écrans ouverts peuvent simplement interroger la plupart de ce dont ils ont besoin à partir de là, sans faire de demandes répétitives pour diverses données de prise en charge 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.

Existe-t-il une autre approche qui pourrait être appropriée ici que d'avoir essentiellement ce type d'objet cache de gestionnaire de données global? Cet objet ne doit pas être officiellement un "Singleton" bien sûr, mais il a un sens conceptuel d'en être un. Qu'est-ce qu'une belle alternative propre ici?

570
Bobby Tables

Il est important de distinguer ici entre instances uniques et modèle de conception Singleton .

Instances uniques sont tout 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 à maintenir, alors vous voudrez certainement avoir une seule instance et la maintenir en vie aussi longtemps que possible.

Le Singleton modèle de conception est un type très spécifique d'instance unique, en particulier celui qui est:

  • Accessible via un champ d'instance statique global;
  • Créé soit lors de l'initialisation du programme, soit lors du premier accès;
  • Aucun constructeur public (ne peut pas instancier directement);
  • Jamais explicitement libéré (implicitement libéré à la fin du programme).

C'est en raison de ce choix de conception spécifique que le modèle présente plusieurs problèmes potentiels à long terme:

  • Incapacité à utiliser des classes abstraites ou d'interface;
  • Incapacité de sous-classe;
  • Couplage élevé à travers l'application (difficile à modifier);
  • Difficile à tester (ne peut pas simuler/se moquer des tests unitaires);
  • Difficile à paralléliser en cas d'état mutable (nécessite un verrouillage important);
  • etc.

Aucun de ces symptômes n'est réellement endémique à des instances uniques, juste le modèle Singleton.

Que pouvez-vous faire à la place? N'utilisez simplement pas le motif Singleton.

Citant la question:

L'idée était d'avoir cet emplacement unique dans l'application qui conserve les données stockées et synchronisées, puis tous les nouveaux écrans ouverts peuvent simplement interroger la plupart de ce dont ils ont besoin à partir de là, sans faire de demandes répétitives pour diverses données de prise en charge 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.

Ce concept a un nom, comme vous l'avez en quelque sorte laissé entendre, mais son incertain. Cela s'appelle un cache . Si vous voulez avoir de la fantaisie, vous pouvez l'appeler un "cache hors ligne" ou simplement une copie hors ligne de données distantes.

Un cache n'a pas besoin d'être un singleton. Il peut-être doit être une seule instance si vous voulez éviter de récupérer 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érents domaines fonctionnels du cache en interfaces distinctes. Par exemple, supposons que vous réalisiez le pire clone YouTube au monde basé sur Microsoft Access:

 MSAccessCache 
 ▲ 
 | 
 + ----------------- + -------- --------- + 
 | | | 
 IMediaCache IProfileCache IPageCache 
 | | | 
 | | | 
 VideoPage MyAccountPage MostPopularPage 

Ici, vous avez plusieurs interfaces décrivant les types de données spécifiques auxquelles une classe particulière pourrait avoir besoin d'accéder - médias, profils utilisateur et pages statiques (comme la première page ). Tout cela est implémenté par un méga-cache, mais vous concevez vos classes individuelles pour accepter les interfaces à la place, donc elles ne se soucient pas du type d'instance qu'elles ont. Vous initialisez l'instance physique une fois, au démarrage de votre programme, puis commencez simplement à passer autour des instances (converties en un type d'interface particulier) via des constructeurs et des propriétés publiques.

Cela s'appelle Injection de dépendance , soit dit en passant; vous n'avez pas besoin d'utiliser Spring ou tout autre conteneur IoC spécial, tant que votre conception de classe générale accepte ses dépendances de l'appelant au lieu de les instancier tout seul ou référence à l'état global.

Pourquoi devriez-vous utiliser la conception basée sur l'interface? Trois raisons:

  1. Cela rend le code plus facile à lire; vous pouvez clairement comprendre des interfaces exactement quelles données les classes dépendantes dépendent.

  2. Si et quand vous réalisez que Microsoft Access n'était pas le meilleur choix pour un back-end de données, vous pouvez le remplacer par quelque chose de mieux - disons SQL Server.

  3. Si et quand vous vous rendez compte que SQL Server n'est pas le meilleur choix pour les médias spécifiquement, vous pouvez interrompre votre implémentation sans affecter aucune autre partie du système . C'est là qu'intervient le véritable pouvoir de l'abstraction.

Si vous voulez aller plus loin, vous pouvez utiliser un conteneur IoC (framework DI) comme Spring (Java) ou Unity (.NET). Presque chaque infrastructure DI fera sa propre gestion de la durée de vie et vous permettra spécifiquement de définir un service particulier comme une seule instance (l’appelant souvent "singleton", mais c'est seulement pour la familiarité). Fondamentalement, ces cadres vous épargnent la plupart du travail de singe consistant à passer manuellement des instances, mais ils ne sont pas strictement nécessaires. Vous n'avez pas besoin d'outils spéciaux pour implémenter cette conception.

Par souci d'exhaustivité, je dois souligner que le design ci-dessus n'est vraiment pas idéal non plus. Lorsque vous traitez un cache (comme vous l'êtes), vous devriez en fait avoir une couche -! entièrement séparée. En d'autres termes, un design comme celui-ci:

 + - IMediaRepository 
 | 
 Cache (générique) --------------- + - IProfileRepository 
 ▲ | 
 | + - IPageRepository 
 + ----------------- + ----------------- + 
 | | | 
 IMediaCache IProfileCache IPageCache 
 | | | 
 | | | 
 VideoPage MyAccountPage MostPopularPage 

L'avantage de ceci est que vous n'avez même jamais besoin de casser votre instance Cache si vous décidez de refactoriser; vous pouvez changer la façon dont le média est stocké simplement en lui fournissant une implémentation alternative de IMediaRepository. Si vous réfléchissez à la façon dont cela s'articule, vous verrez qu'il ne crée toujours qu'une seule instance physique d'un cache, vous n'avez donc jamais besoin de récupérer deux fois les mêmes données.

Rien de tout cela ne veut dire 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 tout.

P.S. 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 serait toujours très similaire à ce qui est illustré ci-dessus, vous ne feriez simplement pas référence aux interfaces individuelles comme Caches. Au lieu de cela, vous les nommeriez Services ou quelque chose de similaire.

826
Aaronaught

Dans le cas que vous donnez, cela ressemble à l'utilisation d'un Singleton n'est pas le problème, mais le symptôme d'un problème - un problème architectural plus important.

Pourquoi les écrans interrogent-ils l'objet de cache pour les données? La mise en cache doit être transparente pour le client. Il devrait y avoir une abstraction appropriée pour fournir les données, et la mise en œuvre de cette abstraction pourrait utiliser la mise en cache.

Le problème est probable que les dépendances entre les parties du système ne sont pas configurées correctement, et cela 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 créer des écrans n'est pas centralisée et il n'y a donc pas de raison claire d'injecter les 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 façon dont l'objet est trouvé pour utilisation.

Un cache devra vivre pendant toute la durée de vie de l'application (pour être utile), de sorte que la durée de vie de l'objet est celle d'un Singleton.

Mais le problème avec Singleton (au moins l'implémentation commune de Singleton en tant que classe/propriété statique), est de savoir comment les autres classes qui l'utilisent vont le trouver.

Avec une implémentation statique de Singleton, la convention consiste à simplement l'utiliser là où c'est nécessaire. Mais cela masque 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 toute la classe consommatrice doit en avoir connaissance est le contrat disponible pour l'utiliser.

48
quentin-starin

J'ai écrit un chapitre entier sur juste cette question. Surtout dans le contexte des jeux, mais la plupart devraient s'appliquer en dehors des jeux.

tl; dr:

Le modèle Gang of Four Singleton fait deux choses: vous donner un accès pratique à un objet de n'importe où et vous assurer qu'une seule instance de celui-ci peut être créée. 99% du temps, tout ce qui vous intéresse, c'est la première moitié de cela, et le fait de rouler le long de la seconde moitié pour l'obtenir ajoute une limitation inutile.

Non seulement cela, mais il existe de meilleures solutions pour donner un accès pratique. Rendre un objet global est l'option nucléaire pour résoudre cela et permet de détruire facilement votre encapsulation. Tout ce qui est mauvais au niveau mondial 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 une meilleure façon de le donner à juste ces objets sans l'exposer à la base de code entière. Autres solutions:

  • Laissez tomber complètement. J'ai vu beaucoup de classes singleton qui n'ont aucun état et ne sont que des sacs de fonctions d'assistance. Ceux-ci n'ont pas du tout besoin d'une instance. Faites-leur simplement des fonctions statiques ou déplacez-les dans l'une des classes que la fonction prend comme argument. Vous n'auriez pas besoin d'une classe Math spéciale si vous pouviez simplement faire 123.Abs().

  • Passez-le. La solution simple si une méthode a besoin d'un autre objet est de le passer. Il n'y a rien de mal à faire passer quelques 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 qu'elles partagent une classe de base, vous pouvez faire de cet objet un membre de la base. Lorsque vous le construisez, passez l'objet. Désormais, les objets dérivés peuvent tous l'obtenir lorsqu'ils en ont besoin. Si vous le protégez, vous vous assurez que l'objet reste encapsulé.

45
munificent

Ce n'est pas un état global en soi qui est le problème.

Vraiment, il suffit de s'inquiéter pour global mutable state. L'état constant n'est pas affecté par les effets secondaires et constitue donc moins un problème.

Le problème majeur avec singleton est qu'il ajoute du couplage et rend ainsi les choses comme les tests difficiles (euh). Vous pouvez réduire le couplage en obtenant 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 des implémentations alternatives pour différentes phases)).

Dans votre situation, je pense que vous pouvez vous en tirer aussi longtemps que votre singleton implémente réellement une interface (afin qu'une alternative puisse être utilisée dans d'autres situations).

Mais un autre inconvénient majeur avec les singletons est qu'une fois qu'ils sont en place, les retirer du code et les remplacer par quelque chose d'autre devient une véritable tâche difficile (il y a à nouveau ce couplage).

// Example from 5 minutes (con't be too critical)
class ServerFactory
{
    public:
        // By default return a RealServer
        ServerInterface& getServer();

        // Set a non default server:
        void setServer(ServerInterface& server);
};

class ServerInterface { /* define Interface */ };

class RealServer: public ServerInterface {}; // This is a singleton (potentially)

class TestServer: public ServerInterface {}; // This need not be.
21
Martin York

Alors quoi? Puisque personne ne l'a dit: Toolbox. C'est si vous voulez variables globales.

L'abus de singleton peut être évité en examinant le problème sous un angle différent. Supposons qu'une application ait besoin d'une seule instance d'une classe et que l'application configure cette classe au démarrage: pourquoi la classe elle-même devrait-elle être responsable d'être un singleton? Il semble tout à fait logique que l'application assume cette responsabilité, car l'application nécessite ce type de comportement. L'application, et non le composant, doit être le singleton. L'application met ensuite une instance du composant à la disposition de tout code spécifique à l'application à utiliser. Lorsqu'une application utilise plusieurs de ces composants, elle peut les regrouper dans ce que nous avons appelé une boîte à outils.

En termes simples, la boîte à outils de l'application est un singleton qui est chargé de se configurer lui-même ou de permettre au mécanisme de démarrage de l'application de le configurer ...

public class Toolbox {
     private static Toolbox _instance; 

     public static Toolbox Instance {
         get {
             if (_instance == null) {
                 _instance = new Toolbox(); 
             }
             return _instance; 
         }
     }

     protected Toolbox() {
         Initialize(); 
     }

     protected void Initialize() {
         // Your code here
     }

     private MyComponent _myComponent; 

     public MyComponent MyComponent() {
         get {
             return _myComponent(); 
         }
     }
     ... 

     // Optional: standard extension allowing
     // runtime registration of global objects. 
     private Map components; 

     public Object GetComponent (String componentName) {
         return components.Get(componentName); 
     }

     public void RegisterComponent(String componentName, Object component) 
     {
         components.Put(componentName, component); 
     }

     public void DeregisterComponent(String componentName) {
         components.Remove(componentName); 
     }

}

Mais devinez quoi? C'est un singleton!

Et qu'est-ce qu'un singleton?

C'est peut-être là que la confusion commence.

Pour moi, le singleton est un objet imposé pour avoir une seule instance seulement et toujours. Vous pouvez y accéder n'importe où, à tout moment, sans avoir besoin de l'instancier. C'est pourquoi il est si étroitement lié à static . A 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 il est alloué automatiquement. Et cela peut et pose des problèmes.

D'après mon expérience, le simple remplacement de static pour Singleton a résolu de nombreux problèmes dans un projet de sac de patchwork de taille moyenne sur lequel je suis. Cela signifie seulement qu'il a une certaine utilité pour les projets mal conçus. Je pense qu'il y a trop discussion si le motif singleton est tile ou pas et je ne peux pas vraiment discuter si c'est effectivement mauvais . Mais il y a toujours bons arguments en faveur du singleton par rapport aux méthodes statiques, en général .

La seule chose dont je suis sûr est mauvais à propos des singletons, c'est quand nous les utilisons tout en ignorant les bonnes pratiques. C'est en effet quelque chose de difficile à 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 qu'il y a juste trop à faire.

Ne vous méprenez pas!

Autrement dit, tout comme vars globaux , les singletons devraient encore = à éviter à tout moment . Surtout parce qu'ils sont trop abusés. Mais les vars globaux ne peuvent pas toujours être évités et nous devons les utiliser dans ce dernier cas.

Quoi qu'il en soit, il existe de nombreuses autres suggestions 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 Localisateur de service comme alternative. Pour moi, c'est essentiellement une " Static Toolbox ", si vous voulez. En d'autres termes, faites du localisateur de service un singleton et vous avez 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 appliquer le problème de singleton qui est de savoir comment il est utilisé, pas le modèle en lui-même.

  • Autres suggérer Modèle d'usine comme alternative. C'était la première alternative que j'ai entendue d'un collègue et nous l'avons rapidement éliminée pour notre utilisation comme var globale. Il a certainement son usage, mais il en va de même pour les singletons.

Les deux alternatives ci-dessus sont de bonnes alternatives. Mais tout dépend de votre utilisation.

Maintenant, impliquer des singletons devrait être évité à tout prix est tout simplement faux ...

  • Aaronaught la réponse suggère de ne jamais utiliser de singletons , pour une série de raisons . Mais ce sont toutes des raisons contre la façon dont il est mal utilisé et maltraité, pas directement contre le modèle lui-même. Je suis d'accord avec tous les soucis sur ces points, comment ne puis-je pas? Je pense simplement que c'est trompeur.

Les incapacités (d'abstraire ou de sous-classe) sont bien là, mais alors quoi? Ce n'est pas fait pour ça. Il n'y a pas incapacité d'interfacer, pour autant que je peux dire . Un couplage élevé peut également être présent, mais c'est simplement parce qu'il est couramment utilisé. Il n'a pas à . En fait, le couplage en soi n'a rien à voir avec le motif singleton. Cela étant précisé, cela élimine également déjà la difficulté de tester. Quant à la difficulté à paralléliser, cela dépend de la langue et de la plateforme donc, là encore, pas de problème sur le pattern.

Exemples pratiques

J'en vois souvent 2 utilisés, à la fois en faveur et contre les singletons. Cache Web (mon cas) et service de journalisation .

La journalisation, certains diront , est un exemple singleton parfait, car, et je cite:

  • Les demandeurs ont besoin d'un objet bien connu auquel envoyer des demandes pour se connecter. Cela signifie un point d'accès global.
  • Étant donné que le service de journalisation est une source d'événements unique à laquelle plusieurs écouteurs peuvent s'inscrire, il ne doit y avoir qu'une seule instance.
  • Bien que différentes applications puissent se connecter à différents périphériques de sortie, la façon dont elles enregistrent leurs écouteurs est toujours la même. Toute personnalisation se fait par les auditeurs. Les clients peuvent demander la journalisation sans savoir comment ni où le texte sera enregistré. Chaque application utiliserait donc le service de journalisation exactement de la même manière.
  • Toute application doit pouvoir s'en tirer avec une seule instance du service de journalisation.
  • Tout objet peut être un demandeur de journalisation, y compris des composants réutilisables, de sorte qu'ils ne doivent pas être couplés à une application particulière.

Alors que d'autres diront qu'il est difficile d'étendre le service de journalisation une fois que vous vous rendez compte qu'il ne devrait en fait pas être qu'une seule instance.

Eh bien, je dis que les deux arguments sont valides. Ici encore, le problème n'est pas lié au modèle singleton. C'est sur les décisions architecturales et la pondération si le refactoring est un risque viable. C'est un autre problème lorsque, généralement, la refactorisation est la dernière mesure corrective nécessaire.

20
cregox

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 qui a une dépendance à ce "gestionnaire" 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 singleton, ce qui peut ne pas être facile.

Si d'un autre côté votre "manager" est injecté dans les composants dépendants via un paramètre constructeur, et que le composant ne connaît pas le type concret du manager, seulement une interface, ou une classe de base abstraite que le manager implémente, alors une unité test pourrait fournir des implémentations alternatives du gestionnaire lors du test des dépendances.

Si vous utilisez IOC conteneurs pour configurer et instancier les composants qui composent votre application, vous pouvez facilement configurer votre IOC conteneur pour créer une seule instance de la "gestionnaire", vous permettant d'obtenir le même, une seule instance contrôlant le cache global des applications.

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 de toute façon)

5
Pete

Un singleton n'est pas fondamentalement mauvais, dans le sens où tout calcul informatique peut être bon ou mauvais. Elle ne peut être que correcte (donne les résultats attendus) ou non. Il peut également être utile ou non, s'il rend le code plus clair ou plus efficace.

Un cas dans lequel les singletons sont utiles est lorsqu'ils représentent une entité vraiment unique. Dans la plupart des environnements, les bases de données sont uniques, il n'y a vraiment qu'une seule base de données. La connexion à cette base de données peut être compliquée car elle nécessite des autorisations spéciales ou la 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. Cela est important lorsque la base de données unique et unique comprend en réalité 4 bases de données, une pour la production, la mise en scène, le développement et les montages de test. Un Database Singleton déterminera à laquelle il doit se connecter, récupérera l'instance unique de cette base de données, la connectera si nécessaire et la renverra à l'appelant.

Quand un singleton n'est pas vraiment un singleton (c'est là que la plupart des programmeurs se fâchent), c'est un global instancié paresseusement, il n'y a aucune possibilité d'injecter une instance correcte.

Une autre caractéristique utile d'un modèle 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 regroupé, ou s'il effectue un test, il peut en créer un nouveau pour chaque appelant ou fournir un objet factice à la place.

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. Il n'y a qu'une seule application que vous êtes, il est donc approprié de la représenter avec un singleton.

L'utilisation d'un singleton comme classe de gestionnaire de données est acceptable tant qu'il est bien conçu. S'il s'agit d'un ensemble de propriétés de données, ce n'est pas mieux que la portée globale. Si c'est un ensemble de getters et setters, c'est mieux, mais toujours pas génial. S'il s'agit d'une classe qui gère vraiment toutes les interfaces vers les données, y compris peut-être la récupération de données distantes, la mise en cache, la configuration et le démontage ... Cela pourrait être très utile.

3
Dan Ray

Les singletons ne sont que la projection d'une architecture orientée services dans un programme.

Une API est un exemple de singleton au niveau du protocole. Vous accédez à Twitter, Google, etc. via ce qui sont 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 mises en cache liées de manière aléatoire, les singletons sont parfaitement logiques.

Les singletons sont un point d'accès au service. L'interface publique d'une bibliothèque de fonctionnalités étroitement liée qui cache peut-être une architecture interne très sophistiquée.

Je ne vois donc pas un singleton aussi différent d'une usine. Le singleton peut avoir des paramètres de constructeur transmis. 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 les tests, vous pouvez insérer votre propre maquette. Cela peut donc être assez flexible.

La clé est en interne dans un programme lorsque j'exécute et j'ai besoin d'un peu de fonctionnalité, je peux accéder au singleton en toute confiance que le service est en place et prêt à l'emploi. Ceci est essentiel lorsqu'il existe différents threads démarrant dans un processus qui doivent passer par une machine d'état pour être considérés comme prêts.

En général, j'envelopperais une classe XxxService qui encapsule un singleton autour de la classe Xxx. Le singleton n'est pas du tout dans la classe Xxx, il est séparé en une autre classe, XxxService. C'est parce que Xxx peut avoir plusieurs instances, bien que ce ne soit pas probable, mais nous voulons toujours avoir une instance de Xxx globalement accessible sur chaque système. XxxService fournit une bonne séparation des préoccupations. Xxx n'a pas à appliquer une politique de singleton, mais nous pouvons utiliser Xxx comme singleton quand nous en avons besoin.

Quelque chose comme:

//XxxService.h:
/**
 * Provide singleton wrapper for Xxx object. This wrapper
 * can be autogenerated so is not made part of the object.
 */

#include "Xxx/Xxx.h"


class XxxService
{
    public:
    /**
     * Return a Xxx object as a singleton. The double check
     * singleton algorithm is used. A 0 return means there was
     * an error. Developers should use this as the access point to
     * get the Xxx object.
     *
     * <PRE>
     * @@ #include "Xxx/XxxService.h"
     * @@ Xxx* xxx= XxxService::Singleton();
     * <PRE>
     */

     static Xxx*     Singleton();

     private:
         static Mutex  mProtection;
};


//XxxService.cpp:

#include "Xxx/XxxService.h"                   // class implemented
#include "LockGuard.h"     

// CLASS SCOPE
//
Mutex XxxService::mProtection;

Xxx* XxxService::Singleton()
{
    static Xxx* singleton;  // the variable holding the singleton

    // First check to see if the singleton has been created.
    //
    if (singleton == 0)
    {
        // Block all but the first creator.
        //
        LockGuard lock(mProtection);

        // Check again just in case someone had created it
        // while we were blocked.
        //
        if (singleton == 0)
        {
            // Create the singleton Xxx object. It's assigned
            // to a temporary so other accessors don't see
            // the singleton as created before it really is.
            //
            Xxx* inprocess_singleton= new Xxx;

            // Move the singleton to state online so we know that is has
            // been created and it ready for use.
            //
            if (inprocess_singleton->MoveOnline())
            {
                LOG(0, "XxxService:Service: FAIL MoveOnline");
                return 0;
            }

            // Wait until the module says it's in online state.
            //
            if (inprocess_singleton->WaitTil(Module::MODULE_STATE_ONLINE))
            {
                LOG(0, "XxxService:Service: FAIL move to online");
                return 0;
            }

            // The singleton is created successfully so assign it.
            //
            singleton= inprocess_singleton;


        }// still not created
    }// not created

    // Return the created singleton.
    //
    return singleton;

}// Singleton  
3
Todd Hoff

Demandez à chaque écran d'intégrer le gestionnaire dans son constructeur.

Lorsque vous démarrez votre application, vous créez une instance du gestionnaire et la transmettez.

Cela s'appelle Inversion de contrôle 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 (bon pour les tests!). Enfin, votre manager mourra avec son propre objet (la classe startup).

Structurez donc votre application comme un arbre, où les éléments ci-dessus possèdent tout ce qui est utilisé en dessous d'eux. N'implémentez pas une application comme un maillage, où tout le monde se connaît et se retrouve grâce à des méthodes globales.

1
Alexander Torstling

OMI, votre exemple sonne bien. Je suggère de prendre en compte les éléments suivants: un objet cache pour chaque (et derrière chaque) objet de données; les objets cache et les objets accesseurs db ont la même interface. Cela donne la possibilité d'échanger des caches dans et hors du code; de plus, il offre un itinéraire d'expansion facile.

Graphique:

DB
|
DB Accessor for OBJ A
| 
Cache for OBJ A
|
OBJ A Client requesting

L'accesseur DB 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 cela fonctionne toujours.

Cela dissocie les choses afin que vous puissiez ajouter de nouveaux caches sans avoir à entrer et modifier un objet Uber-Cache. YMMV. IANAL. ETC.

1
Paul Nathan

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 qu'une application ne changerait pas de taille à moins que vous ne changiez aussi la couleur ... vous pouvez cependant changer la couleur et conserver la taille).

Ce que vous feriez serait d'avoir cette classe mais SUPPRIMER TOUS LES MEMBRES STATIQUES. Ok ce n'est pas nécessaire mais je le recommande. Vraiment, vous venez d'initialiser la classe comme une classe normale et de PASSER le pointeur. Ne dites pas 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, il suffit de le traiter comme ça.

1
user2528

Un peu tard pour la fête, mais enfin.

Singleton est un outil dans une boîte à outils, comme tout le reste. J'espère que vous avez plus dans votre boîte à outils qu'un simple marteau.

Considère ceci:

public void DoSomething()
{
    MySingleton.Instance.Work();
}

contre

public void DoSomething(MySingleton singleton)
{
    singleton.Work();
}
DoSomething(MySingleton.instance);

Le premier cas conduit à un couplage élevé, etc. La deuxième voie n'a aucun problème à décrire @Aaronaught, pour autant que je sache. Tout dépend de la façon dont vous l'utilisez.

1
Evgeni