2009-08-19 7 views
108

Nous avons une classe qui contient des informations de configuration pour l'application. C'était un singleton. Après une revue architecturale, on nous a dit d'enlever le singleton. Nous avons constaté certains avantages à ne pas utiliser singleton dans les tests unitaires, car nous pouvons tester différentes configurations en même temps.Alternative à Singleton

Sans singleton, nous devons faire passer l'instance partout dans notre code. Il devient tellement salissant que nous avons écrit un wrapper singleton. Maintenant, nous portons le même code à PHP et .NET, je me demande s'il y a un meilleur modèle que nous pouvons utiliser pour l'objet de configuration.

Répondre

124

Le Google Testing blog comporte une série d'entrées permettant d'éviter Singleton (afin de créer du code testable). Peut-être que cela peut vous aider:

Le dernier article explique en détail comment déplacer la création de nouveaux objets dans une usine, donc vous pouvez éviter d'utiliser des singletons.Il vaut la peine d'être lu.

En bref, nous déplaçons tous les nouveaux opérateurs vers une usine. Nous regroupons tous les objets de durée de vie similaire dans une seule usine. Dépend de quel outillage/frameworks etc. sont utilisés.

+11

Google testing blog est nécessaire de lecture. – koen

+3

*** Utilisation de l'injection de dépendance pour éviter les singletons – Justin

+0

Ces articles sont aussi bons que les standards de programmation Google C++! –

0

Une classe qui contient uniquement des méthodes statiques et des champs est-elle possible? Je ne sais pas exactement quelle est votre situation, mais cela pourrait valoir la peine d'être examiné.

+1

Si la classe est sans état, elle devrait être une classe statique. – AlbertoPL

+1

Il est en C++ - le modèle est connu comme un monostate. –

15

Le meilleur moyen est d'utiliser un motif Factory à la place. Lorsque vous construisez une nouvelle instance de votre classe (en usine), vous pouvez insérer les données 'globales' dans l'objet nouvellement construit, soit comme une référence à une seule instance (que vous stockez dans la classe de fabrique), soit en copiant les des données dans le nouvel objet.

Tous vos objets contiendront alors les données utilisées pour vivre dans le singleton. Je ne pense pas qu'il y ait une grande différence dans l'ensemble, mais cela peut rendre votre code plus facile à lire.

+1

Je ne suis pas d'accord avec la déclaration "best way", mais +1 pour une bonne alternative. – tylermac

+0

Le problème avec cette approche est que chaque nouvel objet contient (ou des références) ce qui peut potentiellement être un énorme gob de données. var_dump() sur l'un de ces objets contenant des gob résultats très rapidement dans des listes énormes poivré librement avec des avertissements ** récursivité **. Il friggin laid, ne peut pas être terriblement efficace, et rend les choses semblent détraqué. Cependant, je n'ai pas trouvé personnellement un meilleur moyen. J'ai plié la méthode "factory" en utilisant le __construct() pour référencer un global. On a l'impression que tout est plié en quatre pour éviter le redoutable Singleton ... – FYA

+2

@EastGhostCom: on pourrait aussi bien utiliser le singleton et arrêter d'essayer de rendre les choses difficiles pour soi :) – gbjbaanb

5

Je pourrais dire l'évidence ici, mais y a-t-il une raison pour laquelle vous ne pouvez pas utiliser un cadre d'injection de dépendance tel que Spring ou Guice? (Je crois que Spring est également disponible pour .NET maintenant). Ainsi, l'infrastructure peut contenir une seule copie des objets de configuration, et vos beans (services, DAO, etc.) n'ont pas à se soucier de la rechercher.

C'est l'approche que je prends habituellement!

0

Peut-être pas très propre non plus, mais vous pouvez passer peut-être les bits d'information que vous voulez avoir changé la méthode qui crée le singleton - au lieu d'utiliser

public static Singleton getInstance() { 
    if(singleton != null) 
     createSingleton(); 
     return singleton; 
    } 
} 

vous pouvez appeler createSingleton(Information info) directement au démarrage de l'application (et dans les méthodes setUp des tests unitaires).

4

Si vous utilisez Spring Framework, vous pouvez simplement créer un bean standard. Par défaut (ou si vous définissez explicitement scope="singleton"), une seule instance du bean est créée et cette instance est renvoyée chaque fois que le bean est utilisé dans une dépendance ou récupéré via getBean().

Vous obtenez l'avantage de l'instance unique, sans le couplage du motif Singleton.

+1

Oh l'ironie - use (singleton) Spring haricots pour remplacer votre singleton ... –

0

Avec les outils d'injection de dépendance/ioc, il est souvent possible d'obtenir des performances/optimisations singleton en demandant au conteneur di/ioc d'utiliser le comportement singleton pour la classe requise (comme une interface IConfigSettings) en créant une seule instance de la classe. Cela pourrait être encore substitué pour tester

Alternativement, on pourrait utiliser une usine pour créer la classe et retourner la même instance chaque fois que vous demandez - mais pour le tester pourrait retourner une écrasa/version moqué

4

L'alternative passe dans ce dont vous avez besoin au lieu de demander un objet pour des choses.

0

Possibilité d'examiner la configuration en tant qu'interface de rappel. Ainsi, votre configuration du code sensible regardera:

MyReuseCode.Configure(IConfiguration) 

Code système-init regardera:

Library.init(MyIConfigurationImpl) 
4

n'accumulent pas RESPONSABILITES à un seul objet de configuration car elle sera acquittée dans un très grand objet à la fois difficile à comprendre et fragile. Par exemple, si vous avez besoin d'un autre paramètre pour une classe particulière, modifiez l'objet Configuration, puis recompilez toutes les classes qui l'utilisent. C'est un peu problématique. Essayez de refactoriser votre code pour éviter un objet commun, global et grand, Configuration. Passer des paramètres uniquement requis pour les classes de clients:

class Server { 

    int port; 

    Server(Configuration config) { 
     this.port = config.getServerPort(); 
    } 

} 

devrait être à remaniée avec:

class Server { 

    public Server(int port) { 
     this.port = port; 
    } 
} 

un cadre d'injection de dépendance aidera beaucoup, mais il n'est pas nécessaire stricly.

+0

Oui c'est vraiment un bon point. Je l'ai fait avant. Mon grand objet de configuration implémentait des interfaces telles que MailServiceConf, ServerConf .. que framework d'injection de dépendances transmettait la configuration aux classes afin que mes classes ne dépendaient pas d'un grand objet Configuration. – mcaaltuntas

0

Vous pouvez utiliser une structure d'injection de dépendance pour faciliter la transmission de l'objet de configuration. Un bon est ninject qui a l'avantage d'utiliser le code plutôt que xml.

0

Vous pouvez accomplir le même comportement de singleton en utilisant des méthodes statiques. Steve Yegge l'explique très bien dans this post.

+0

En fait, l'article est assez bon, et il ne dit pas que vous devriez utiliser des méthodes statiques à la place. Il déclare à la place que les méthodes statiques ne sont que des singletons et à la fin il recommande d'utiliser le modèle de méthode d'usine: "Je vais terminer en disant que si vous avez encore besoin d'utiliser des objets Singleton, pensez à utiliser le pattern Factory Method. Au lieu de cela ... " – FrankS

-1

Les singletons ne sont pas mauvais, mais le motif de conception est défectueux. J'ai une classe que je veux seulement créer une seule instance pendant l'exécution mais je veux créer plusieurs instances isolées pendant le test unitaire pour assurer des résultats déterministes. DI utilisant Spring, etc, est une très bonne option mais pas la seule option.

+1

Quelles sont les autres options? – hakre