Il y a 9 ans -
Temps de lecture 5 minutes
Design Pattern en scala – Singleton
Cet article est le premier d’une série d’articles dont le but est de montrer aux lecteurs comment implémenter en Scala les différents design patterns connus.
Chaque article étudiera un ou plusieurs design patterns, rappellera la définition de chacun, et montrera les différences d’implémentation entre Scala et Java.
Je couvrirai dans cette série d’articles les 23 design patterns cités par le GOF et quelques autres design patterns propres à Scala et/ou provenant du monde de la programmation fonctionnelle.
Définition
Le design pattern singleton « garantit qu’une classe n’a qu’une seule instance et fournit un point d’accès de type global à cette classe » d’après le GoF. Il permet d’avoir une seule instance d’une classe dans un espace et un temps donné. Il faut noter ici la différence entre le singleton dit « JVM » et les singletons que j’appelle de « plateforme », tels que les singletons Java EE, Spring ou autre. Lorsque nous parlons d’un singleton JVM, nous spécifions que la classe est chargée une seule fois dans la JVM. Le singleton Spring quant à lui implique juste l’existence d’une instance unique au sein d’un contexte Spring. Un singleton Java EE est unique dans le conteneur.
Cas d’utilisation
C’est le pattern le plus utilisé, souvent à tort par les développeurs juniors comme seniors. Aujourd’hui, on en voit très souvent dans le code. Tout est devenu prétexte pour introduire un singleton : les classes utilitaires (helper), les classes de services, etc.
Pourtant les cas d’utilisation du Singleton sont peu nombreux et doivent le rester. Parmi les cas d’utilisation, on peut citer :
- le gestionnaire d’affichage (DisplayManager),
- le gestionnaire de pilotes de base de données (DriverManager),
- le runtime,
- etc.
Bref une règle simple peut vous aider à prendre votre décision : un singleton est souvent lié à une ressource unique. Le développeur doit se demander si une classe qui n’a que des membres statiques (attributs et méthodes) et un constructeur privé n’est pas suffisante (ce qui est généralement le cas).
Implémentation
Java
Plusieurs solutions existent en Java. Les principales différences entre les implémentations tournent autour de la simplicité d’écriture, l’aspect thread safety dans un environnement multi-threadé et l’initialisation tardive.
Vous trouverez ci-dessous trois de ces solutions :
La solution triviale
Elle consiste à créer une classe finale munie d’un constructeur privé, une instance statique, finale et privée et une méthode statique publique retournant une référence à l’instance statique.
public final class Singleton { private static Singleton instance = new Singleton(); // accès global public static Singleton getInstance() { return instance; } // constructeur privé. Obligatoire pour empêcher l'instantiation ailleurs . private Singleton() { } // méthodes et attributs }
Cette solution est simple à écrire et à comprendre. Elle ne présente aucun problème de synchronisation, puisque le chargement des classes est thread safe. Le seul souci qu’elle présente est que l’instance statique est initialisée au chargement de la classe ce qui rend le chargement coûteux. En effet, elle n’implémente pas l’initialisation tardive.
La solution enum
De plus en plus de développeurs préfèrent une implémentation à base d’enum à l’implémentation précédente.
public enum Singleton { ONE; // méthodes et attributs }
Cette solution est encore plus simple que la précédente. Elle présente les mêmes avantages et inconvénients en plus du fait quelle n’est compatible qu’avec Java 5 et plus (et oui il y a encore des gens qui développent avec Java 1.3 et 1.4 !).
La solution avec inner class
C’est une adaptation de la première solution qui ajoute une classe privée chargée d’instancier le singleton.
public final class Singleton { private static class SingletonLoader { private static Singleton instance = new Singleton(); } // accès global public static Singleton getInstance() { return SingletonLoader.instance; } // constructeur privé. Obligatoire pour empêcher l'instantiation ailleurs . private Singleton() { } // méthodes et attributs }
Cette solution reste simple, thread-safe, implémente le chargement tardif et elle fonctionne sur toutes les versions des JVM. C’est ma préférée entre les solutions présentées.
Scala
Les singletons dans Scala sont appellés Singleton Objects. Le but premier des Singleton Objects est de remplacer les membres statiques (attributs et méthodes). En effet, dans Scala, il n’y a pas de mot-clé static, il n’y a ni attribut de classe ni méthode de classe. Pour en avoir, il faut créer un Singleton Object. Voici comment :
object Singleton{ // méthodes et attributs } // Voici comment l'utiliser depuis une autre classe, un autre object ou dans un script val singleton = Singleton
Scala compile un object en deux classes :
- Singleton : une classe finale qui ne contient que des méthodes statiques. Les méthodes statiques enveloppent l’appel à la vraie implémentation contenue dans l’autre classe générée.
- Singleton$ : une classe finale avec un constructeur privé et une instance statique d’elle même.
Avec le code décompilé (avec JD-GUI) ci dessous on en déduit que l’implémentation scala se rapproche beaucoup de la solution Java avec inner class.
public final class Singleton$ implements ScalaObject { public static final MODULE$; static{ new(); } private Singleton$(){ MODULE$ = this; } } public final class Singleton { }
Comme la dernière implémentation du pattern en Java, cette solution est thread safe et implémente l’initialization paresseuse.
Au-delà des détails de l’implémentation du singleton, il est important de rappeler que Scala distingue deux types de Singleton Object :
- Le Companion Object : un Companion Object est défini dans le même fichier qu’une classe portant le même nom. La classe et son objet compagnon peuvent accéder mutuellement à leurs membres même privés.
- Le Standalone Object : le Standalone Object est un objet défini seul.
Dans les deux cas, les singletons en Scala sont des « citoyens de première classe », pour les anglophones « first-class citizens ». Ce qui veut dire que ces entités peuvent :
- Hériter (en Scala on dit plutôt mixer) de traits ou de classes. Mais ils ne peuvent pas être hérités !
- Redéfinir (overwrite) les membres hérités.
- Être utilisés comme n’importe quel autre type.
Conclusion
Entre thread-safety et initialization paresseuse, l’implémentation du pattern Singleton n’est pas la plus intuitive en Java. Malgré cela, le pattern est l’un des plus utilisés. Scala l’intègre nativement en assurant la thread safety et l’initialization paresseuse. Ceci rend son utilisation encore plus facile. Cette simplicité d’implémentation ne doit pas inciter les développeurs à l’utiliser à tort n’importe où dans leur code.
Commentaire
3 réponses pour " Design Pattern en scala – Singleton "
Published by mejdi , Il y a 9 ans
et qu’en est-il de l’aspect « serialisation » dans ces différentes implémentations et dans scala ?
Published by Sebastien Lorber , Il y a 9 ans
J’aurais aimé plus de détails sur les mauvais cas d’usage des singletons.
Tu parles de remplacer les singletons par des classes avec des membres static mais la plupart des reproches qu’on fait aux singletons sont liés au couplage, et on fait généralement les memes reproches aux membres static, en plus du fait qu’ils sont également pas toujours évident a tester (cf pas mal des frameworks de mocking qui ne supportent pas le mock de methodes static)
Perso petite préférence pour l’enum.
D’ailleurs j’aime bien étendre pas mal mes enums quand c’est possible, comme de véritables classes (au final ce ne sont qu’un ensemble de singletons partageant une interface commune sur lesquels on peut facilement itérer).
C’est pas forcement top en terme de couplage mais parfois ca simplifie quand même pas mal la vie.
D’ailleurs j’avais deja vu des mecs qui parlaient d’injecter des services spring dans les enum (a la fin du chargement du contexte spring par ex). Bon l’idée est interessante mais ca me parait un peu dangereux quand même…
Bonne soirée
Published by Aurélien , Il y a 8 ans
C’est une très bonne initiative de montrer comment implémenter en java/scala les design patterns cités par le GOF !
Avez-vous le temps de continuer cette série d’article ?
Merci !