Parmi tous les Design Patterns, le plus connu est sûrement le Singleton. Malheureusement, son implémentation n’est pas aussi triviale qu’il n’y parait en Java. Voici donc quelques exemples d’implémentation…

Eager initialization

Si l’application a toujours besoin de cette instance et que sa création ne mobilise pas trop de ressource, il est acceptable de l’écrire ainsi :

public class EagerSingleton {
  private static final EagerSingleton instance = new EagerSingleton();

  private EagerSingleton() {}

  public static EagerSingleton getInstance() {
    return instance;
  }
}

Cependant, il est souvent nécessaire de retarder le chargement de l’instance. De plus, lors de l’implémentation d’un composant réutilisable, il ne faut pas imposer le chargement de tous les singletons à l’application utilisatrice si elle n’en a pas besoin !

Lazy initialization

C’est pourquoi la première implémentation réfléchie mais naïve qui vient à l’esprit est la suivante :

public class LazyInitializationSingleton {
  private static LazyInitializationSingleton instance;

  private LazyInitializationSingleton(){}

  public static LazyInitializationSingleton getInstance() {
    if (instance==null) {
      instance = new LazyInitializationSingleton();
    }
    return instance;
  }
}

Elle semble juste. Cependant, elle n’est pas thread-safe. Dans un contexte multi-thread, plusieurs instances peuvent être créées.

Double check thread-safe

Une fois que la problématique multi-thread est prise en compte, le développeur est tenté de l’implémenter de cette manière :

public class ThreadSafeSingleton {
  private static ThreadSafeSingleton instance;

  private ThreadSafeSingleton() {}

  public static ThreadSafeSingleton getInstance() {
    if (instance==null) {
      synchronized (ThreadSafeSingleton.class) {
        if (instance==null) {
          instance = new ThreadSafeSingleton();
        }
      }
    }
    return instance;
  }
}

Cette implémentation donne l’impression de fonctionner. Or selon, le compilateur, la JVM ou l’OS, le comportement peut différer. A tel point que le “double check locking” est considéré comme un antipattern. Java 5.0 étend la sémantique du mot-clé volatile pour garantir le “flush” des caches avant l’usage de la variable. En utilisant cette solution, vous acceptez d’avoir un code moins performant.

L’énumération

Dans la seconde édition d’Effective Java, Joshua Bloch propose d’utiliser les enum pour implémenter un singleton :

public enum EnumSingleton {
  INSTANCE;

  public static void doStuff() {
    // do stuff
  }
}

Cette solution est terriblement simple mais possède un inconvénient majeur : les énumérations sont moins flexibles que les classes (héritage, sérialisation, etc…).

Bill Pugh Singleton

Cette implémentation tente de réconcilier toutes précédentes au moyen d’une classe imbriquée :

public class BillPughSingleton {
  private BillPughSingleton() {}

  static private class SingletonHelper {
    private static final BillPughSingleton INSTANCE = new BillPughSingleton();
  }

  public static BillPughSingleton getInstance() {
    return SingletonHelper.INSTANCE;
  }
}

Elle reprend le principe de la Eager initialization sans avoir l’inconvénient identifié sur le lazy-loading puisque la classe imbriquée n’est connue de personne. Enfin, cette implémentation est thread-safe sans avoir à recourir à des mots-clés du langage tels que synchronized, volatile ou enum.

Avant de terminer, sachez que si vous utilisez des frameworks d’injection de dépendances tel que Spring Framework, il est possible de le laisser gérer vos classes comme des singletons.

Pourquoi vous avoir présenté toutes ces implémentations ? Simplement pour montrer que l’implémentation d’un Design Pattern, aussi simple soit-il, n’est pas aussi triviale qu’il n’y parait. Et la première question à se poser est : “Pourquoi utilise-t-on ce modèle de conception ?”. Il est également recommandé de bien documenter l’implémentation afin d’expliciter le contexte d’utilisation. 😉

Histoire à suivre…