Découvrez l'utilisation du pattern singleton avec C#, afin de restreindre l'instanciation d'une classe à un seul objet.
Prérequis : les bases du C# - POO
Niveau : intermédiaire
Avant toute chose si vous ne savez pas ce qu'est un pattern design, ou patron de conception, sachez que c'est globalement une façon d'organiser les classes entre elles afin de répondre à un besoin particulier de l'application.
Dans un premier temps nous allons nous pencher sur la théorie, puis nous verrons une implémentation en C# .
Architecture du pattern
L'idée primordiale de ce pattern est de s'assurer qu'il n'existe qu'une seule instance d'une classe, à tout moment du programme. De plus cette classe doit disposer d'une méthode globale afin de pouvoir accéder justement à l'instance.
En gros, on crée un objet d'une classe A donnée, et si à un autre moment on redemande un autre objet de cette même classe A, on s'assure de donner la seule instance existante.
Voici un diagramme UML d'une classe utilisant la pattern singleton, on notera que la variable instance pointe sur elle même, ce que l'on peut traduire par :
instance = this;
Le constructeur (ici Singleton()) doit être "privated" afin que seule la méthode d'accès spéciale static getInstance() puisse contrôler si on a ou pas déjà une instance de cette classe. Cette méthode renverra d'ailleurs un objet de type Singleton.
- Si instance est null, alors on utilisera le constructeur pour créer le premier ET le dernier objet Singleton.
- Si on a déjà une instance, on renverra l'objet Singleton.
Notez que le Singleton possède donc d'un accès dit global, de part la nature "static" même de la méthode d'accès.
Implémentation en C#
Il est temps de mettre cette architecture Singleton en place avec notre langage C# !!
Un premier jet nous amène
public class LoadingManager{
private static LoadingManager instance = null;
public static LoadingManager getInstance()
{
if(instance == null)
{
instance = new LoadingManager();
}
return instance;
}
}
Tout semble correct, non ?
Et bien en fait avec cette implémentation on peut quand même créer plusieurs instances de LoadingManager.
Par exemple il suffit ici de faire deux appels au constructeur par défaut de la classe. En effet si vous le ne saviez pas, toute classe possède un constructeur par défaut, même si on a rien écrit dans la classe.
LoadingManager manager1 = new LoadingManager();
LoadingManager manager2 = new LoadingManager();
Ainsi notre erreur provient du fait de ne pas avoir redéfini notre constructeur, et qui plus est, de le mettre en privated, afin qu'aucune autre classe puisse appeler le constructeur. On bloque ainsi l'appel à "new" qui permet de créer des instance de la classe.
Voici ci-dessous la version corrigée.
public class LoadingManager{
private static LoadingManager instance = null;
private LoadingManager(){} //on redéfinit notre constructeur en privated
public static LoadingManager getInstance()
{
if(instance == null)
{
instance = new LoadingManager();
}
return instance;
}
}
On notera qu'ici on utilise le principe du Lazzy Loading (chargement fainéant), c'est à dire du chargement décalé en quelque sorte.
Ainsi on attend le premier appel à la méthode getInstance avant de créer l'objet unique LoadingManager.
De plus le Lazzy Loading permet d'avoir un contrôle sur le chargement en mémoire de la classe.
Interêts et limitations du pattern
On peut ainsi utiliser ce pattern si :
- une classe doit être absolument unique afin d'éviter des accès concurrentiels. Par exemple dans le cas d'une classe chargée d'accéder aux fichiers de la mémoire.
- Plusieurs classes ont besoin d'accéder aux méthodes de notre classe unique. Plutôt que chacune de ces classes clientes possèdent une instance (dont l'état interne risque d'être différent), nous utilisons un appel statique permettant d'appeler cette méthode n'importe où dans le code et ce sans instance.
Mais après tout pourquoi s'embêter à utiliser ce pattern ? Ne peut-on pas s'en passer ou trouver des alternatives répondant pour autant au même besoin ?
C'est une question qui fait débat car le fait de disposer d'un accès global par la méthode getInstance() est séduisant. Mais cela encourage son utilisation démesurée et pourrait donner lieu à une prolifération de Singleton dans vos projets.
De plus a-t-on forcément besoin que notre classe soit accessible partout ?
On peut sûrement organiser notre code différemment...selon les cas l'utilisation du pattern Singleton se fait à tort, et une autre organisation aurait pu être choisie.
En effet ce n'est pas parce que ce pattern existe qu'il faut l'utiliser. Certains développeurs vont même jusqu'à s'en passer totalement.
Libre à vous de vous faire votre propre opinion.
Retrouvez comment utiliser le pattern singleton avec Unity.