Le design pattern Singleton est parmi les plus courants en programmation orientée objet et quand on crée des applications en JavaScript, il peut manquer… Pour bien comprendre tout ce qui va suivre, vous aurez besoin de deux prérequis (si vous ne les avez pas, vous allez pouvoir les rattraper grâce à deux autres tutoriels de votre serviteur), à savoir les closures et plus particulièrement les variables statiques. À noter que je ne rappelle pas ici la façon dont on utilise la POO en JavaScript.
Quelle utilité ?
Avant de nous lancer dans l’implémentation du design pattern Singleton, il faudrait déjà répondre à une question : à quoi sert-il ? Prenons l’exemple d’un jeu vidéo. En POO, pour bien faire les choses, on a bien pris soin de séparer les différentes entités utiles au bon fonctionnement de notre jeu : une classe pour le moteur de jeu, une classe pour le moteur graphique, une classe s’occupant de stocker les images, etc. Seulement voila, la classe de stockage d’images, par exemple, sera probablement amenée à être utilisée plus d’une fois dans le jeu.
Problème : vous créerez un nouvel objet à chaque utilisation, ce qui fait que les images précédemment stockées ne le seront plus… Embêtant. Une solution possible serait d’utiliser les variables globales, mais si vous avez de bons réflexes alors vous vous direz « quoi de plus dégueulasse ? » et vous aurez raison ! Alors quoi, on crée une instance que l’on passe en argument, de fonction en fonction ? Vous vous en doutez bien, évidemment que non, ce n’est absolument pas pratique et nous savons tous qu’un programmeur est avant tout un gros fainéant !
D’où l’intérêt du Singleton. Une classe Singleton est une classe qui possède une instance unique durant toute l’exécution du programme (d’où son nom). C’est une classe qui stocke sa propre instance et qui transmet cette instance quand on le lui demande : ça paraît assez fou dit comme ça, mais vous allez comprendre comment une telle chose est possible avec la suite.
Implémentation en JavaScript
Avant même de créer notre classe, il faut nous demander comment nous allons stocker son instance. Elle sera stockée à l’intérieur de notre classe et, bien évidemment, elle devra toujours avoir son état précédent quand on la récupère, sans que ce soit un nouvel objet. Pour réaliser ça, il nous faut bien sûr une variable statique, et c’est pour ça qu’il fallait réfléchir au stockage avant la création de la classe car, comme vous le savez, une variable statique en JavaScript nécessite un peu de changement… Voici donc notre classe :
var Singleton = (function() {
var instance = null;
})();
Bien sûr, vous choisissez le nom que vous voulez, Singleton n’est qu’un exemple. Avant toute chose, il nous faut créer le constructeur de notre classe, là où nous allons définir toutes ses méthodes et propriétés (non statiques) :
var Singleton = (function() {
var constructeur = function() {
this.methodePublique = function() {
}
var methodePrivee = function() {
}
var prop1, prop2;
}
var instance = null;
})();
Comprenez bien que tout ce qui se situe en-dehors du constructeur est statique donc définissez tout ce qui compose votre classe à l’intérieur du constructeur.
Nous avons donc notre classe mais aucun moyen d’y accéder… Ce qui est plutôt fâcheux donc nous allons y remédier tout de suite en créant un accesseur qui nous renverra notre instance. Si vous avez remarqué, nous ne stockons en fait rien du tout ici dans notre variable Singleton, il faut donc commencer par renvoyer quelque chose, et ce quelque chose ce sera un objet :
var Singleton = (function() {
var constructeur = function() {
this.methodePublique = function() {
}
var methodePrivee = function() {
}
var prop1, prop2;
}
var instance = null;
return new function() {
}
})();
On arrive au bout ! L’accesseur sera en fait une propriété publique de cet objet que l’on renvoie. Dans cet accesseur, on ne trouvera pas seulement une ligne s’occupant de retourner notre instance, pour la raison évidente suivante : si on ne fait que ça, l’instance n’est jamais créée ! Il faut donc avant tout insérer une condition afin de voir si l’instance a été créée (c’est facile : si elle n’a jamais été créée, la variable contenant notre instance vaut null). Si elle n’a pas été créée alors on s’en occupe.
var Singleton = (function() {
var constructeur = function() {
this.methodePublique = function() {
}
var methodePrivee = function() {
}
var prop1, prop2;
}
var instance = null;
return new function() {
this.getInstance = function() {
if (instance == null) {
instance = new constructeur();
instance.constructeur = null;
}
return instance;
}
}
})();
Et le tour est joué ! Vous noterez une petite subtilité en plus : l’attribution de la valeur null à instance.constructeur. Cette affectation permet simplement d’être sûr que l’on ne pourra pas recréer un nouvel objet Singleton, en détruisant le constructeur.
Comment utiliser tout ça ?
Cette structure sera la même pour tous vos singletons : tout ce que vous avez à faire, c’est définir toutes les méthodes et propriétés dans le constructeur, le reste ne doit pas bouger. En ce qui concerne la façon dont on accède à l’instance, c’est simple :
Singleton.getInstance().methodePublique();
À chaque fois, vous n’aurez qu’à utiliser Singleton.getInstance(). Cette écriture est assez lourde, aussi, si vous en avez besoin plusieurs fois dans une fonction, n’hésitez pas à stocker votre instance :
var s = Singleton.getInstance();
Le JavaScript est fait de telle sorte que la variable s pointera bien sur le même objet que Singleton.getInstance(). Aucune copie ne sera faite et toutes les modifications faites sur s seront également faites sur l’instance stockée dans la classe.
Une conclusion, parce qu’on ne va pas se quitter comme ça
Vous savez donc utiliser le design pattern Singleton. Si vous n’avez pas forcément tout compris, n’hésitez pas à relire ou à poser vos questions en commentaire.
15 commentaires
Le singleton EST une variable globale :)
Peux tu montrer un exemple d’héritage basé sur cette implémentation ?
Pour un lecteur-POO qui utilise un langage comme c++/java/flash action script3/c# etc.
Une variable statique c’est quelque de bien précis et c’est justement l’approche alternative du pattern Singleton.
Ce serait bien de clarifier ce point dans ton article.
@YopSolo: Variable globale, je dirais pas exactement. Ce qui est global c’est la classe, mais ça on pourrait difficilement faire autrement.
L’instance de la classe, dont on se sert, elle n’est pas globale, elle est encapsulée dans la classe singleton.
Concernant l’héritage, j’y reviendrai dans un futur article, ici je souhaitais simplement expliquer comment fonctionnait ce design pattern.
Tout à fait c’est encapsulé dans une classe globale, avec cette remarque je voulais souligner que ce pattern aussi pratique et courant soit’il, ne ne résout aucun des problèmes liées aux variables globales.
« Unfortunately, it’s more placebo than cure. If you scan the list of problems that globals cause, you’ll notice that the Singleton pattern doesn’t solve any of them. That’s because a Singleton is global state, just encapsulated in a class. »
c’est tiré de cet excellent site :
http://gameprogrammingpatterns.com/singleton.html
Salut,
Pourquoi faire un return d’une fonction qui instancie l’instance si le tout est dans une fonction appelée directement?
Soit c’est une fonction normale (je veux dire pas de type « (function(){})() ») et c’est justifié, soit tu peut directement faire un return de constructeur().
Ainsi dans ta variable singleton tu n’aura que l’objet créer par le constructeur.
@Faucheuse: Un Singleton, comme toute classe, n’est qu’une fonction et notre variable Singleton doit donc contenir une fonction, d’où le return.
Tu ne m’a pas compris, je pense que ce code fais la même chose :
var Singleton = (function() {
var constructeur = function() {
this.methodePublique = function() {
}
var methodePrivee = function() {
}
var prop1, prop2;
}
return constructeur();
})();
@Faucheuse: Non, ton code revient à créer une classe normale si je ne fais pas d’erreur, il faut faire un peu plus pour avoir un véritable singleton.
@Jérémy, ton Singleton est correcte mais je préfère l’approche ci-après.
Moins de code, plus propre, je trouve. Qu’en penses-tu?
/**
* author: @hrechard (14/12/2013)
*/
;(function(fn) {
« use strict »;
fn.Function.prototype[« getInstance »] = function() {
if (typeof this.instance == « undefined ») this.instance = this;
return this.instance;
}
})(window);
—
Il existe, de même, le renvoi direct de l’instance comme ceci, mais moins élégant.
function myFunction() {
if (typeof myFunction.instance == « undefined ») myFunction.instance = this;
[…]
return myFunction.instance;
}
@+
@FuZZyLine: J’ai du mal à voir où on interdit la création directe de l’objet ?
Mauvaise manip, je te prie de m’excuser.
Pour répondre à ta question : au niveau du test de type.
Par exemple, en plus simple et en se basant sur un ternaire:
return (bfTest) ? myFunc.instance : MyFonction.instance = new myFunc();
ou bfTest est le flag de test boolean. Tiens, ca m’intéresserait bien que
tu fasses un article sur les Multitons en JS.
A te lire bientôt, @+
@FuZZyLine: Bah en fait tu interdis bien la création de deux instances via getInstance(), mais en quoi ça nous empêche de créer notre propre objet sans passer par getInstance() ? Je sais pas si je passe à côté d’un truc mais je vois pas l’interdiction dans ton code… :/
Concernant les Multitons, je ne connaissais pas, n’ayant jamais eu l’occasion d’en utiliser, je vais regarder ça, mais je garantis pas de faire ça rapidement… ^^’
@Jérémy: Rien n’empêche l’instanciation directe
mais protège l’instance quand celle-ci est déclarée via getInstance.
inst1 = new myFonction();
inst2 = new myFonction();
instA = myFonction.getInstance();
instB = myFonction.getInstance();
Donneront bien un rendu différent.
inst1 & inst2 auront bien une instance différente chacun en revanche
instA & instB auront la même.
En revanche :
function myFunction() {
if (typeof myFunction.instance == « undefined ») myFunction.instance = this;
[…]
return myFunction.instance;
}
Là, dans cette archi, aucune possibilité d’instanciation si une existe déjà.
Sauf, que de ces deux possibilités, comme l’ai dit, la première est plus élégante.
@+ ;)
@FuZZyLine: L’élégance est subjective :P
Je sais pas trop, le but d’un singleton c’est qu’il n’y ait qu’une seule instance possible dans tout le programme, là on peut en avoir plusieurs du coup (rien n’oblige à ne passer que par getInstance())…
@Jérémy: « c’est qu’il n’y ait qu’une seule instance… » Pas tout à fait, en fait et c’est là que la différence se fait je pense.
Un programme peut avoir autant de Singleton que tu veux. Le singleton ne s’applique qu’à une Class définie (Function en JS). Une instance Singleton est unique dans son contexte d’origine mais utilisé dans tout le projet.
http://fr.wikipedia.org/wiki/Singleton_%28patron_de_conception%29
Et c’est justement ce que j’ai proposé. Dans cette méthode c’est bien le cas… en tout cas si ET uniquement si l’appel se fait via getInstance. Sinon la fonction renverra une instance différente.
Ce qui peut te dérouter, peut-être, c’est d’être passé par le prototypage mais c’est plus confortable.
Hésite pas à me contacter via Twitter si veux plus d’infos, @+ ;)
Vu comme ça, pourquoi pas effectivement. J’ai toujours utilisé les singletons comme étant une seule et même instance dans tout le programme, donc je n’avais jamais exploré l’autre possibilité, mais ça se défend, oui.