JavaScriptSingleton

Ce bref texte se veut un exemple de rédaction d'un singleton simple en JavaScript. Notre singleton de démonstration sera un générateur d'identifiants uniques, par souci de simplicité. Pour des détails sur le schéma de conception singleton en tant que tel, ou pour des comparatifs avec des exemples de la même pratique dans d'autres langages, référez-vous à ../Developpement/Schemas-conception.html#singleton

Exemple concret

Donnons d'office le code tout entier, que nous analyserons par la suite élément par élément :


var GénérateurId = (function () {
   var instance;
   function ZeGénérateur() {
      this.cur = 0;
      this.prochain = function () {
         return this.cur++;
      };
   }
   function createInstance() {
      var singleton = new ZeGénérateur();
      return singleton;
   }
   return {
      getInstance : function() {
         if (!instance) {
            instance = createInstance();
         }
         return instance;
      }
   };
})();
function testGénérerIdentifiant() {
   document.getElementById("identifiantGénéré").innerHTML = "<strong>" + GénérateurId.getInstance().prochain() + "</strong>";
}

Vous pouvez vous convaincre que le tout fonctionne en appuyant à quelques reprises sur le bouton ci-dessous et en constatant que l'identifiant généré change chaque fois, suivant une croissance monotone.

Id généré :

Les éléments clés de l'implémentation de ce singleton vont comme suit. Nous prendrons d'abord une vue d'ensemble, quelque peu aérienne, puis nous comblerons les « trous » en insérant progressivement les éléments de code qui permettront de mieux saisir cette pratique dans le détail.

Vu de haut, l'idée maîtresse de cette implémentation est que le singleton, associé à la variable GénérateurId, est un « objet » résultant de l'exécution d'une fonction qui est créée de manière anonyme pour les besoins de la cause. On peut constater cela du fait que la fonction est créée entre deux parenthèses, puis que le résultat de cette création anonyme est appelé (paire de parenthèses suivant la déclaration de la fonction) de manière à retourner ce qui servira de singleton en pratique.

À l'utilisation, par la suite, on accède au singleton en appelant la méthode getInstance() de GénérateurId. Cette méthode s'assure que le véritable objet ne sera effectivement créé qu'une seule fois.

Le singleton, en pratique, n'est pas vraiment GénérateurId mais est plutôt un « objet » caché à l'intérieur de GénérateurId, et qui n'est exposé qu'en passant à travers le service getInstance(). Dans notre cas, cet « objet caché » est une instance de ZeGénérateur; nous y reviendrons plus bas.

var GénérateurId = (function () {
   //
   // ... le code du singleton se trouvera ici
   //
})();
function testGénérerIdentifiant() {
   document.getElementById("identifiantGénéré").innerHTML = "<strong>" + GénérateurId.getInstance().prochain() + "</strong>";
}

La mécanique d'instanciation du singleton s'exprime en trois étapes :

  • La variable qui servira de référence vers le singleton, nommée ici instance. Cette variable sera inaccessible à l'extérieur de la fonction anonyme (c'est une simple variable locale);
  • La fonction createInstance(), elle aussi inaccessible à l'extérieur de la fonction anonyme (c'est une fonction locale à la fonction anonyme). Le rôle de createInstance() est d'instancier le ZeGénérateur. Cette fonction ne garantit pas l'unicité de cette instanciation, cependant. Notez que la variable locale singleton n'est pas nécessaire ici (elle est utile à fins documentaires), donc que createInstance() pourrait se limiter à un simple return new ZeGénérateur();
  • Enfin, le return qui conclut la fonction anonyme retourne en fait un « objet » ayant une seule méthode, getInstance(). Cet « objet » anonyme constitue une fermeture autour de la variable instance et de la fonction createInstance(), ce qui permet à sa méthode getInstance() de procéder à l'instanciation, et de s'assurer que cette instanciation soit unique.
var GénérateurId = (function () {
   var instance;
   //
   // ... la définition de ZeGénérateur se trouvera ici
   //
   function createInstance() {
      var singleton = new ZeGénérateur();
      return singleton;
   }
   return {
      getInstance : function() {
         if (!instance) {
            instance = createInstance();
         }
         return instance;
      }
   };
})();

Enfin, l'« objet » qui ne sera instancié qu'une seule fois sera un ZeGénérateur. Dans notre cas, cet objet aura un attribut (cur) et une méthode (prochain), la méthode ayant pour rôle de retourner à chaque appel un identifiant distinct.

Notez que dans cette implémentation, l'attribut et la méthode sont tous deux publics, ce qui est souhaitable dans le cas de la méthode mais l'est moins dans le cas de l'attribut.

À votre avis, pourrait-on faire mieux?

var GénérateurId = (function () {
   //
   // ...
   //
   function ZeGénérateur() {
      this.cur = 0;
      this.prochain = function () {
         return this.cur++;
      };
   }
   //
   // ...
   //
})();

Valid XHTML 1.0 Transitional

CSS Valide !