Accès rapide :
Présentation du design pattern Singleton
La représentation UML du Singleton
Mise en oeuvre du design pattern Singleton en Java
Approche « thread unsafe »
Approche « thread safe »
Prise en charge du pattern dans Eclipse
Définition d'un template Eclipse
Test du template singleton dans Eclipse
Le design pattern Singleton permet de s'assurer qu'une classe ne puisse produire qu'une seule et unique instance.
Sans aller chercher très loin, si l'on considère l'IDE Eclipse, il ne peut afficher qu'une seule et unique fenêtre principale par JVM. Or, il peut être utile de retrouver cette fenêtre de n'importe ou dans le programme Eclipse. La mise en oeuvre d'un desing pattern Singleton est donc tout indiquer pour ce besoin.
Voici une représentation générique du design pattern Singleton. Elle a été réalisée avec le modeleur UML Eclipse Papyrus.
Comme vous le constater, on retire la possibilité de produire des instances de la classe en rendant le constructeur de la classe privé.
Pour récupérer notre unique instance, nous utiliserons la méthode statique getInstance
, qui elle pourra éventuellement invoquer le
constructeur. Pour maintenir l'unique instance en mémoire, nous utiliserons l'attribut instance
qui sera statique et privé.
Il faut bien comprendre que le design pattern Singleton ne traite que de l'unicité de l'instance. Pour autant, vous pouvez ensuite rajouter tout ce que vous souhaitez (attributs, méthodes...) à votre classe, en fonction de vos besoins.
Plusieurs « Implementation Patterns » Java peuvent être considérés pour implémenter le design pattern Singleton. La principale différences entre ces implémentations réside dans l'aspect « thread safe », ou non.
Une première approche naïve serait la suivante : c'est celle qu'on retrouve le plus souvent dans les exemples de singleton proposés sur Internet.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package fr.koor.samples; public final class Singleton { private static Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if ( instance == null ) { instance = new Singleton(); } return instance; } } |
final
, dit autrement on ne peut pas en dériver, car la construction d'objet est privatisée.
Or, en cas d'héritage, un constructeur invoque obligatoirement son constructeur parent, ce qui ne serait pas possible ici.
Cette approche présente l'avantage de ne créer l'instance du singleton, que si elle est requise (si elle est demandée).
Mais cette approche présente aussi un inconvénient majeur : elle est non « thread safe ». Dans de très rares situations, plusieurs instances pourraient être produites si au moins deux threads demande à obtenir le singleton à des instants extrêmement proches. Vous ne devez donc considérer cette approche que et uniquement que si votre programme ne met pas en jeu de thread supplémentaire.
Et voici un petit exemple d'utilisation de notre classe de singleton.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
package fr.koor.samples; public class Start { public static void main(String[] args) { Singleton ref1 = Singleton.getInstance(); Singleton ref2 = Singleton.getInstance(); System.out.println( ref1 ); // On vérifie qu'on ait bien qu'une unique instance. System.out.println( ref1 == ref2 ); } } |
Et voici les résultats produits par cet exemple.
fr.koor.samples.Singleton@6ff3c5b5 true
Deux variations peuvent être considérées pour implémenter un singleton thread safe. La première variation que je vous propose permet de ne produire le singleton que s'il est réellement utilisé. Mais du coup, elle est un peu plus complexe à écrire.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
package fr.koor.samples; public final class Singleton { private static volatile Singleton instance = null; private Singleton() { } public static Singleton getInstance() { if ( instance == null ) { synchronized( Singleton.class ) { if ( instance == null ) { instance = new Singleton(); } } } return instance; } } |
synchronized
permet de garantir que son bloc d'instruction ne peut être exécuté que par un unique thread à la fois.
Attention, la synchronisation est un mécanisme coûteux en temps : il faut donc faire cela que si c'est strictement nécessaire.
if
sur le test de l'instance n'est pas inutile. Le premier permet d'éviter, une fois le singleton produit, de synchroniser pour rien.
Le second permet réellement de garantir qu'il n'y ait qu'une unique instance produite.
Une dernière solution consiste à produire l'instance dès le chargement de la classe en mémoire.
Cette instanciation est garantie comme étant thread safe par l'environnement d'exécution Java : ça nous évite des lignes de code pour rien.
Et cerise sur le gâteau, le code est plus performant car, par la suite, on ne teste plus la valeur null
pour l'attribut statique !
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
package fr.koor.samples; public final class Singleton { private static final Singleton INSTANCE = new Singleton(); private Singleton() { } public static Singleton getInstance() { return INSTANCE; } } |
final
afin de garantir que personne puisse modifier cet attribut.
Il est possible d'automatiser la génération du singleton dans Eclipse. Pour ce faire, il suffit de définir un nouveau « template » dans l'éditeur Java d'Eclipse. Réalisons cette manipulation ensemble.
Tout d'abord, commencez par ouvrir les préférences d'Eclipse : cliquez sur le menu « Window » puis sélectionnez « Preferences ». La boîte de dialogue des préférences doit s'ouvrir. Dans le panneau de gauche, sélectionnez le noeud « Java / Editor / Templates », comme le montre la capture d'écran ci-dessous.
Cliquez sur le bouton « New » en haut à droite pour créer un nouveau template. La boîte de dialogue suivante doit apparaître.
Appelez votre template « singleton » et assurez-vous qu'il soit lié au contexte Java. Vous pouvez lui donner une description, puis copier le code suivant dans la zone « Pattern ».
private static final ${primary_type_name} INSTANCE = new ${primary_type_name}(); private ${primary_type_name}() { // TODO : ${cursor} } public static ${primary_type_name} getInstance() { return INSTANCE; }
${...}
sont appelées de variables de template. On peut aussi les générer en cliquant sur le bouton
« Insert Variable... ».
Testons maintenant notre template. Pour ce faire, commencez par créer une nouvelle classe. Appelez-la Workbench
(ou tout autre nom qui vous
conviendra), puis placez-vous dans le corps de la classe et tapez les premières lettres du template suivies de la séquence de touches CTRL+Space.
Un assistant vous proposera le template : si vous confirmez son utilisation, le code du singleton sera inséré dans votre classe.
Et voici le code finalement généré. Merci Eclipse ;-)
Il ne reste plus qu'à compléter votre classe avec le code souhaité.
Améliorations / Corrections
Vous avez des améliorations (ou des corrections) à proposer pour ce document : je vous remerçie par avance de m'en faire part, cela m'aide à améliorer le site.
Emplacement :
Description des améliorations :