Catégorie:
Fréquence d'utilisation:
Difficulté:

Singleton garantit qu’une classe n’a qu’une seule instance et fournit un point d’accès global à cette instance.

Description du problème

Certaines applications possèdent des classes qui doivent être instanciées une seule et unique fois. C’est par exemple le cas d’une classe qui implémenterait un pilote pour un périphérique, ou encore un système de journalisation. En effet, instancier deux fois une classe servant de pilote à une imprimante provoquerait une surcharge inutile du système et des comportements incohérents.

On peut alors se demander comment créer une classe, utilisée plusieurs fois au sein de la même application , qui ne pourra être instancié qu’une seule fois ?

Une première solution, régulièrement utilisée, est d’instancier la classe dès le lancement de l’application dans une variable globale (c’est à dire une variable accessible depuis n’importe quel emplacement du programme). Cependant cette solution doit être évitée car en plus d’enfreindre le principe d’encapsulation elle comporte de nombreux inconvénients. En effet, rien ne garantit qu’un développeur n’instanciera pas une deuxième fois la classe à la place d’utiliser la variable globale définie. De plus, on est obligé d’instancier les variables globales dès le lancement de l’application et non à la demande (ce qui peut avoir un impact non négligeable sur la performance de l’application). Enfin, lorsqu’on arrive à plusieurs centaines de variables globales le développement devient rapidement ingérable surtout si plusieurs programmeurs travails simultanément.

Mais si on n’utilise pas ce stratagème comment faire ? C’est simple il suffit d’utiliser le design pattern Singleton.

 

Définition de la solution

L’objectif est d’ajouter un contrôle sur le nombre d’instances que peut retourner une classe.

La première étape consiste à empêcher les développeurs d’utiliser le ou les constructeur(s) de la classe pour l’instancier. Pour cela il suffit de déclarer privé tous les constructeurs de la classe. Attention dans certains langages une classe sans constructeur possède un constructeur implicite par défaut (c’est notamment le cas de Java). Il faut donc que celui-ci soit déclaré explicitement en privé.

Une fois cette étape accomplie, il est possible d’instancier cette classe uniquement depuis elle même, ce qui n’a pas beaucoup de sens. Comment allons nous faire pour permettre aux développeurs de l’utiliser ?

Nous allons construire un pseudo constructeur. Pour cela il faut déclarer une méthode statique qui retournera un objet correspondant au type de la classe. L’avantage de cette méthode par rapport à un constructeur, est que l’on peut contrôler la valeur que l’on va retourner. Le fait que cette méthode soit déclarée statique permet de l’appeler sans posséder d’instance de cette classe. A noter que, par convention, ce pseudo constructeur est nommé getInstance.

Pour en finir avec le concept de base du Singleton voyons comment implémenter cette méthode.

Tout d’abord il faut créer un attribut statique qui va permettre de stocker l’unique instance de la classe. Ensuite, dans le pseudo constructeur on va tester cet attribut. Si celui-ci est nul alors on crée une instance de la classe et on stocke sa valeur dans cet attribut. Sinon c’est que l’attribut possède déjà une instance de la classe. Dans tous les cas la méthode retourne la valeur de l’attribut possédant l’unique instance de la classe.

 

Diagramme UML

 Diagramme UML du design pattern Singleton

 

Extension : Multiton

Une variante du Singleton existe elle est nommée Multiton. Même si celle-ci est régulièrement utilisée il ne s’agit pas ici d’un design pattern officiel du Gang of Four. Comme nous venons de la voir le pattern Singleton garantit qu’une classe n’a qu’une seule instance. Le Multiton reprend ce principe en garantissant qu’il existe une seule instance par clé pour une classe. Ainsi il est par exemple possible de créer une classe qui possédera deux instances identifiées par deux clés différentes.

Pour cela on remplace l’attribut uniqueInstance par un tableau associatif (il s’agit d’un type abstrait de données composé d’un ensemble fini de clefs et d’un ensemble fini de valeurs, où chaque clef est associée à une valeur) ou table de hashage . On associe alors une clé à une instance. Puis on transforme la fonction getInstance qui prend maintenant en paramètre une clé. Si cette clé existe dans le tableau associatif, on retourne l’instance correspondante sinon on crée une nouvelle instance associée à la clé que l’on ajoute à uniqueInstance. Ainsi on peut facilement gérer un ensemble d’instances avec leurs clés associées.

 

Conséquences

Jusqu’à maintenant nous avons passé sous silence un problème qui peut mettre en péril l’implémentation du pattern Singleton : le multithread (capacité pour un programme de lancer plusieurs traitements simultanés c'est à dire processus). En effet, si l’on implémente de façon basique le pattern Singleton, dans le cas d’un programme multithread, on peut se retrouver avec une classe Singleton possédant plusieurs instances.

Le problème réside dans l’enchaînement des instructions. Un premier processus exécute la fonction getInstance constate que l’attribut uniqueInstance est nul. Un deuxième processus s’exécute et lui aussi constate (via getInstance) que l’attribut uniqueInstance est nul. Il va donc créer une instance et retourner celle-ci. Lorsque le premier processus va reprendre son exécution il va à son tour créer une nouvelle instance (étant donné qu’il a déjà effectué le test sur l’attribut) et retourner celle-ci. On se retrouve alors avec deux instances pour une classe Singleton.

Pour résoudre ce problème, on dispose de deux solutions. La plus simple consiste à instancier l’attribut uniqueInstance dès sa déclaration dans la classe. Ainsi l’implémentation de la méthode getInstance se limite à retourner l’attribut uniqueInstance. Cette approche est fiable et simple à mettre en place mais on perd l’instanciation à la demande.

L’autre approche consiste à s’assurer que la fonction getInstance ne pourra être exécutée que par un seul processus à la fois. Chaque langage dispose de sa spécification pour indiquer la particularité de cette méthode. Par exemple, en Java on utilisera le mot clef « synchornized ». Cette solution bien que satisfaisante, peut réduire les performances d’un programme multithread. Il existe alors des stratagèmes suivant les langages pour pallier à cette lacune.

On peut alors se demander quand doit-on mettre en place ces solutions spécifiques aux multithread. Le meilleur conseil est de toujours implémenter le pattern Singleton comme si le programme utilisait le multithread. Car même si ce n’est pas le cas aujourd’hui, il se peut que dans le futur le multithread apparaisse dans votre application.

Exemples d'implémentations: 
Commentaires

c’est tellement bien fait que j’ai envi de te demander d’autres tuto....les threads par exemple.

merci en tt cas pour ces tutos simples et pertinents.

Pour être honnête je ne suis pas habitué à remercier quelqu’un pour un article ou autres, mais faut dire que c’est très bien expliquer contrairement à d’autres "tutos" sur des technique de programmation ! Je te remercie, grâce à toi je sais maintenant concrètement quand et comment il faut utiliser certains patrons de conception, en espérant que tu en ajoute d’autres !!

Alors de la part de tout les développeurs qui apprennent sans dire merci, MERCI :p

Je ne suis pas un expert des design patterns mais j'entends depuis peu que le singleton est a bannir car il revient en fait à utiliser des variables globales.

D'ailleurs dans Zend Framework 2 tous les getInstance() ont été supprimés et remplacés par un système d'injection de dépendance.

Merci, tout d`abord pour le boulot effectué.

J'entend souvent parler de l'utilisation abusive des singleton, et je la comprend.

Mais pourrais tu donner quelques renseignements sur ces techniques qui evite de devoir recourrir au singleton.

merci

Trop bon tuto !! La lecture se fait toute seule, les bons termes sont employés aux bons endroits, la preuve tu n'as même pas besoin de schema ou de code pour expliquer le fonctionnement du Singleton même si celui-ci reste assez simple. Je dois écrire que 2 ou 3 fois par an sur des forums et là je suis obligé de m'exprimer, je t'encourage donc vivement à continuer à faire partager ta vision des patterns parceque tu t'exprime vraiment bien,  il n'y a aucune ambiguïté dans tes explications, sort nous d'autres tuto, on attend que ça !!!!!! 

Le Singleton pose un problème de taille, il est très simple d'utilisation, en revanche ils introduise un état global à l'instance (équivalent d'une variable globale). Le problème majeur de l'état global c'est qu'il peut-être modifier de n'importe où sans réellement le savoir (quand on est plusieurs sur un même dev c'est fréquent). Mais il est aussi impossible de tester (test unitaire) un état global pour les mêmes raisons. De plus le Singleton pose un problème de responsabilité. On lui donne la responsabilité de s'instancier ce qui ne respecte pas du tout les principes d'un développement SOLID (les 5 principes de base de la POO). 

Aujourd'hui on parle plutôt de "Singleton" ou "singleton". Le second (avec un petit "s") n'est pas réellement un singleton mais plutôt une instance managé par une classe Manager. Il est presque aussi facile de mettre en place ce pattern Manager qu'un Singleton.

Le principe de fonctionnement d'un manager de singleton :On instancie le manager en début de programme, et on injecte cette instance partout ou il est nécessaire d'accéder aux singleton qu'il contiendra. Lors de l'instanciation du Manager on lui passe une configuration qui lui permet de faire le liens entre un appelle à une instance et une classe a instancier (exemple un tableau avec en clé le nom du singleton, en valeur le nom de sa classe), le futur singleton sera instancié sur demande (Lazzy Loading). Le Manager lorsque vous lui demanderez votre singleton, instanciera la classe s'il ne possède aucune instance, et retournera l'instance.

Pour aller plus loin, ce Manager peut-être parfaitement utilisé pour en faire des multitons aussi en changeant légèrement la logique.

Un exemple d'utilisation de ce pattern est le ServiceManager de ZF2, qui a pour rôle de fournir des instances sous forme de service accessible depuis n'importe quel controller via un ServiceLocator.

Bonjour a tous,Singleton est le seul DP du GoF dont la solution tient en une seule classe. Alors pourquoi Singleton est, malgré tout, un DP au meme titre que les autres? 

Ajouter un commentaire