Publié par

Il y a 8 années -

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.

Publié par

Commentaire

3 réponses pour " Design Pattern en scala – Singleton "

  1. Publié par , Il y a 8 années

    et qu’en est-il de l’aspect « serialisation » dans ces différentes implémentations et dans scala ?

  2. Publié par , Il y a 8 années

    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

  3. Publié par , Il y a 6 années

    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 !

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Xebian, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.