Introduction▲
Tout d'abord je pense qu'il est utile de préciser ce qu'est un pattern, il s'agit en fait d'un modèle utilisé en génie logiciel. Un pattern est donc une manière de procéder en programmation, il définit comment doivent s'articuler certains éléments d'un bout de code ou d'une application. Il existe des dizaines de patterns couvrant de très nombreux domaines du génie logiciel. Ils sont le plus souvent créés grâce à des retours d'expérience, et des moyens de procéder largement éprouvés et reconnus, ils peuvent s'apparenter aux « bonnes manières de faire ». Les design patterns sont un ensemble de patterns s'intéressant à la conception de logiciels, comment ils doivent être conçus et quels modèles ils doivent reprendre. Dans cet article nous allons étudier un pattern très connu et très utilisé, il s'agit du Singleton. Il est tellement utilisé que peut-être vous l'avez déjà utilisé sans même le savoir.
I. Présentation du pattern singleton▲
Le Singleton est un modèle de conception. Son but est relativement simple, il doit permettre de limiter le nombre d'instances d'une classe à une ou quelques-unes. En effet, dans de nombreuses occasions nous devons être sûrs qu'il n'existe pas plus d'une seule instance pour une classe donnée, cela peut être par exemple le cas d'un objet gérant une connexion à une base de données, ou encore d'un objet écrivant dans un fichier de log… Le Singleton, par un principe simple qui va nous permettre de s'assurer qu'il n'existe qu'une seule instance d'une classe donnée, en fait c'est le Singleton lui-même qui effectue cet autocontrôle et va agir en fonction de la situation. Ainsi, si aucune instance n'existe alors une instance sera créée, et, si une instance existe déjà alors, il nous retournera une référence vers cette instance déjà existante.
La caractéristique majeure d'un Singleton est que le constructeur de cette classe est privé (Private en VB .Net), il n'est donc pas accessible comme doit normalement l'être le constructeur d'une classe. En effet, dans l'ordre normal des choses un constructeur doit être public, pour qu'on puisse instancier notre classe en appelant sa méthode New().
Puisque le constructeur n'est pas Public, il va falloir avoir au moins une méthode publique pour que l'on puisse utiliser notre Singleton. C'est cette méthode publique qui concrètement va vérifier si une instance existe déjà ou non. Cette méthode, sera le seul et unique moyen pour un développeur, et donc une application, de créer une instance de cette classe implémentant le design pattern Singleton.
Un autre détail qui a son importance c'est le fait que l'on doive déclarer une variable locale qui aura pour rôle de stocker la référence à l'instance unique de notre Singleton. Regardons maintenant comment concrètement implémenter le pattern Singleton avec Visual Basic 2005 et C# 2.0.
II. Utiliser le pattern singleton avec .Net▲
Voici un exemple de classe implémentant le pattern Singleton :
Option
Explicit
On
Option
Strict
On
Public
Class
MonSingleton
' Variable locale pour stocker une référence vers l'instance
Private
Shared
instance As
MonSingleton =
Nothing
' Le constructeur est Private
Private
Sub
New
(
)
'
End
Sub
' La méthode GetInstance doit être Shared
Public
Shared
Function
GetInstance
(
) As
MonSingleton
' Si pas d'instance existante on en crée une...
If
instance Is
Nothing
Then
instance =
New
MonSingleton
End
If
' On retourne l'instance de MonSingleton
Return
instance
End
Function
End
Class
public
class
MonSingleton
{
// Variable locale pour stocker une référence vers l'instance
private
static
MonSingleton instance =
null
;
// Le constructeur est Private
private
MonSingleton
(
)
{
//
}
// La méthode GetInstance doit être Static
public
static
MonSingleton GetInstance
(
)
{
// Si pas d'instance existante on en crée une...
if
(
instance ==
null
)
{
instance =
new
MonSingleton
(
);
}
// On retourne l'instance de MonSingleton
return
instance;
}
}
L'implémentation ci-dessus du Pattern Singleton est correcte, mais présente un petit défaut, en effet, elle n'est pas thread-safe, c'est-à-dire que rien n'empêche plusieurs thread d'utiliser une instance de notre Singleton en même temps. Pour contourner ce problème, nous allons utiliser un mot clé Synlock en VB.Net et Lock en C# pour faire en sorte qu'une telle chose ne soit pas possible. Voyons sans plus tarder comment appliquer cela dans le code :
Option
Explicit
On
Option
Strict
On
Public
Class
MonSingleton
' Variable locale pour stocker une référence vers l'instance
Private
Shared
instance As
MonSingleton =
Nothing
Private
Shared
ReadOnly
mylock As
New
Object
(
)
' Le constructeur est Private
Private
Sub
New
(
)
'
End
Sub
' La méthode GetInstance doit être Shared
Public
Shared
Function
GetInstance
(
) As
MonSingleton
Synclock
(
mylock)
' Si pas d'instance existante on en crée une...
If
instance Is
Nothing
Then
instance =
New
MonSingleton
End
If
' On retourne l'instance de MonSingleton
Return
instance
End
Synclock
End
Function
End
Class
public
class
MonSingleton
{
// Variable locale pour stocker une référence vers l'instance
private
static
MonSingleton instance =
null
;
private
static
readonly
object
mylock =
new
object
(
);
// Le constructeur est Private
private
MonSingleton
(
)
{
//
}
// La méthode GetInstance doit être Shared
public
static
MonSingleton GetInstance
(
)
{
lock
((
mylock)) {
// Si pas d'instance existante on en crée une...
if
(
instance ==
null
)
{
instance =
new
MonSingleton
(
);
}
// On retourne l'instance de MonSingleton
return
instance;
}
}
}
Désormais notre Singleton est réellement thread-safe et il est impossible, même à des threads différents, de créer plusieurs instances de cette classe, il y a une vérification lors de l'instanciation et un verrou en mémoire sur l'objet.
Remarque : attention, Synlock/Lock est certes efficace pour rendre notre Singleton thread-safe mais il faut garder à l'esprit que cette manière de procéder peut suivant les conditions être couteuse en termes de performances, comme d'habitude en développement il faudra donc évaluer le pour et le contre. Mais, à mon avis, cette légère perte de performance vaut largement la sécurité et la robustesse que l'on apporte à notre Singleton.
Maintenant que notre Singleton est implémenté, voyons comment le démontrer. Pour cela, voici le code d'une petite application console qui va vérifier que tous les objets de types MonSingleton correspondent bien à une seule et même instance. Pour cela nous allons utiliser la méthode Equals() que chaque objet .Net implémente et qui permet de comparer l'égalité ou non de deux objets.
Option
Explicit
On
Option
Strict
On
Imports
SingletonDeveloppez
Module
Module1
Sub
Main
(
)
Dim
objet1 As
MonSingleton =
Nothing
objet1 =
MonSingleton.GetInstance
(
)
Dim
objet2 As
MonSingleton =
Nothing
objet2 =
MonSingleton.GetInstance
(
)
If
objet1.Equals
(
objet2) Then
' Notre Singleton fonctionne, il n'y à qu'une seule instance.
Console.WriteLine
(
"Les deux instances sont identiques."
)
End
If
Console.ReadLine
(
)
End
Sub
End
Module
using
SingletonDeveloppez;
class
Module1
{
public
void
Main
(
)
{
MonSingleton objet1 =
null
;
objet1 =
MonSingleton.
GetInstance
(
);
MonSingleton objet2 =
null
;
objet2 =
MonSingleton.
GetInstance
(
);
if
(
objet1.
Equals
(
objet2))
{
// Notre Singleton fonctionne, il n'y a qu'une seule instance.
Console.
WriteLine
(
"Les deux instances sont identiques."
);
}
Console.
ReadLine
(
);
}
}
Conclusion▲
Nous avons vu dans cet article une manière d'implémenter simplement le design pattern Singleton avec VB.Net et en C#. En suivant ce pattern vous voyez à quel point il est simple de contrôler le nombre d'instances qui peuvent exister au même moment pour une classe donnée. Toute l'astuce de ce pattern est que le contrôle du nombre d'instances s'effectue au sein même de la classe et non par un code annexe qui le rendrait peu fiable. Un autre avantage qu'apporte ce pattern est le fait que la modification du processus d'instanciation de la classe n'aura pas d'impact sur le reste de l'application et du code existant, en effet l'instanciation de la classe est entièrement gérée par la classe elle-même grâce au constructeur Private et à la méthode GetInstance().
Un grand merci à AGM26 pour la relecture de cet article, ainsi qu'à l'équipe Dotnet pour son aide.