Norme de conception Singleton en Java

AVERTISSEMENT: Cet article a été écrit en 2001. Depuis, l'utilisation de singletons a été réduite par l'usage de conteneurs web. Sur un serveur, les objets sont configurés dans des fichiers xml, qui décrivent les objets à instancier et surtout leurs relations. La seule contrainte est de respecter la norme Java beans pour que les objets soient configurables et accessibles par des setters et getters standard. Ce qui suit reste tout de même valable.

Les normes de conception (Design Pattern) concrétisent des abstractions efficaces pour des développements courants.

Une classe Singleton fournit une instance unique d'un objet d'usage général, comme une connexion, avec une méthode getInstance() qui peut porter un nom spécifique.

Les approches statique et singleton ne doivent pas être mélangées. Dans une approche singleton, la plupart des méthodes doivent être liées à l'instance créée. Ceci peut par exemple permettre à la classe de fournir un pool d'objets au lieu d'un objet unique sans changement de code pour les objets utilisateurs.

Principes et exemples

En Java

L'initialisation d'une classe singleton peut être longue et est souvent gérée dans un Thread séparé. Elle ne bloque alors pas la suite de l'application. L'obtention d'une instance doit en revanche attendre qu'un objet pleinement fonctionnel soit disponible.

Une utilisation classique est la fourniture d'une connexion à une base de données.

Snippet exemple

Un snippet est un modèle de code utilisé par JBuilder pour créer le squelette d'un source. Avec un autre outil, copier le source et remplacer %class% par le nom de la classe à créer.

Pour installer ce snippet dans JBuilder

Source Singleton.snippet
//<Exclude>
//<Target text="Nom de classe" pattern=%class% default=Singleton1>
//<Package>
//</Exclude>

/**
 * Implémentation générique de la norme de conception (design pattern) Singleton.<br>
 * Il importe de ne pas rendre statiques les données qui dépendent de l'instance.<p>
 * La méthode statique <a href="#getInstance()">getInstance()</a>
 * renvoie une instance unique et prête à fonctionner pour l'objet.
 * P. Larreya, Janvier 2001
 */
public class %class% implements Runnable {
  static private %class% instance;

  private Thread thread;
  private Object data;  // donnée fournie à titre d'exemple

/**
 * Lee constructeur d'un singleton ne doit pas être appelé directement.
 * Initialiser et passer par <a href="#getInstance()">getInstance()</a>
 * pour obtenir un objet.
 */
  private %class%(Object data) {
    this.data = data;
  }
/**
 * Initialisation à appeler avant tout <code>getInstance()</code>
 * <ul>
 * <li><code>synchonized</code> bloque <code>getInstance()</code>
 * <li>création et lancement d'un thread d'initialisation
 * <li>sortie de la méthode. <code>getInstance()</code> peut être appelée
 * <li>lors de l'appel, <code>getInstance()</code> attend la fin du
 *      thread d'initialisation.
 *</ul>
 */
  public static synchronized void init(Object data) {
    if (instance != null) {
      // ou initialiser au premier appel
      throw new RuntimeException("%class%.init() : déjà initialisé.");
    }
    instance = new %class%(data);
    instance.initInstance();
  }

  private void initInstance() {
    thread = new Thread(this);
    thread.start();
  }

/**
 * Renvoie l'instance unique et prête à fonctionner
 */
  public static synchronized %class% getInstance() {
    if (instance == null) {
      throw new RuntimeException("%class%: pas initialisé.");
    }
    try {
      instance.thread.join(); // initialisation terminée.
    }
    catch (InterruptedException ex) {
      System.out.println("Interruption pendant l'obtention d'un %class%");
      ex.printStackTrace();
    }
    return instance;
  }

/**
 * Taches d'initialisation d'une instance, réalisée dans un thread séparé.
 */
  public void run() {

    // coder ici l'initialisation d'une instance singleton

  }

/**
 * Texte affichable
 */
  public String toString() {
    return this.getClass().getName()+": "+data.toString();
  }

}

Le mécanisme d'initialisation avec un découpage en init() et initInstance() n'est pas directement lié à la notion de singleton mais s'inspire d'une approche utilisée par les initialisations CORBA.

Patrick Larreya, Janvier 2001, Février 2006.