Publié lundi 31 mars 2008 11:20 par Luke77

Singleton générique

Tout d'abord, c'est quoi un Singleton ? Pour résumer très vite (Google est ton ami si tu veux plus de détails), un singleton est une instance unique d'une classe qui sera utilisée tout au long d'un processus. Le but de cette démarche est surtout pour moi de gagner en rapidité lors de l'écriture et aussi lors de l'exécution.

Imaginons que j'ai une classe MyClass et à l'intérieur une méthode DoThings. Si je veux appeler cette méthode je suis obligé de faire :

   1: MyClass myClass = new MyClass(); 
   2: myClass.DoThings();

et cela chaque fois que je voudrai appeler la méthode. C'est une perte de temps dans l'écriture du code lui-même mais aussi une perte de temps dans l'exécution puisqu'à chaque fois il devra instancier une nouvelle fois la classe (et tout ce que cela implique niveau mémoire etc). Dans ce cas-là, on pourrait se dire qu'il suffirait de mettre DoThings en méthode statique. De manière générale je suis un peu contre mettre les choses en static sauf si l'utilité est réellement prouvée. Dans notre cas, c'est seulement parce que l'on est feignant, donc je m'y refuse.

La méthode la plus simple, et la plus répandue aussi je crois, pour obtenir le singleton serait de définir des méthodes statiques dans la classe pour effectuer cela.

   1: public class MyClass
   2: {
   3:     private static MyClass _singleton;
   4:  
   5:     static MyClass()
   6:     {
   7:         _singleton = new MyClass();
   8:     }
   9:  
  10:     public static MyClass GetInstance()
  11:     {
  12:         return _singleton;
  13:     }
  14:  
  15:     public void DoThings()
  16:     {
  17:     }
  18: }

Le constructeur statique instancie une fois la classe et la stocke dans une variable statique elle aussi. A chaque appel de la méthode statique GetInstance, on retourne toujours la même instance. Ainsi là où l'on avait les deux lignes définies plus haut, on a plus que ceci :

   1: MyClass.GetInstance().DoThings();

Il me semble que c'est déjà pas mal non ? Et en plus ca marche très bien. Le seul petit problème, c'est que l'on doit se taper la variable, le constructeur et la méthode dans chaque classe où l'on souhaite avoir le singleton. La solution ? Les génériques !

Grâce aux génériques on va pouvoir se créer des singletons une bonne fois pour toutes, et à un seul endroit en plus. Sans plus attendre, le code :

   1: public static class Singleton<T> where T : class, new()
   2: {
   3:     private static T _singleton;
   4:  
   5:     static Singleton()
   6:     {
   7:         _singleton = new T();
   8:     }
   9:  
  10:     public static T GetInstance()
  11:     {
  12:         return _singleton;
  13:     }
  14: }

Le code indique que la classe prendra un argument en générique qui, grâce à la clause where, devra être une classe, et avoir un constructeur public par défaut. Le reste du code est identique au code présenté précédemment. L'appel quant à lui change un peu, il faut désormais appeler la méthode DoThings de cette manière :

   1: Singleton<MyClass>.GetInstance().DoThings();

Je le concède, cela prends plus de caractères pour faire l'appel, cependant cela devient beaucoup plus simple lors de l'écriture des classes. En plus on peut le customiser pour posséder plusieurs singleton d'une même classe. Par exemple, imaginons un système qui impose d'aller taper une base de données dans un cas, et une autre base de données dans un autre ; il nous faudrait deux singletons, un pour chaque base de données.

Pour cela on peut définir une seconde classe Singleton qui permettrait d'obtenir un singleton en fonction d'un paramètre fourni lors de l'appel à GetInstance. Sans plus attendre, le code qui permet ça :

   1: public static class Singleton<T, P> where T : class, new()
   2: {
   3:     private static Dictionary<P, T> _singleton;
   4:     private static ReaderWriterLockSlim _lock;
   5:  
   6:     static Singleton()
   7:     {
   8:         _singleton = new Dictionary<P, T>();
   9:         _lock = new ReaderWriterLockSlim();
  10:     }
  11:  
  12:     public static T GetInstance(P param)
  13:     {
  14:         T t;
  15:         _lock.EnterUpgradeableReadLock();
  16:         try
  17:         {
  18:             if (!_singleton.TryGetValue(param, out t))
  19:             {
  20:                 _lock.EnterWriteLock();
  21:                 try
  22:                 {
  23:                     if (!_singleton.ContainsKey(param))
  24:                     {
  25:                         t = new T();
  26:                         _singleton.Add(param, t);
  27:                     }
  28:                 }
  29:                 finally
  30:                 {
  31:                     _lock.ExitWriteLock();
  32:                 }
  33:             }
  34:         }
  35:         finally
  36:         {
  37:             _lock.ExitUpgradeableReadLock();
  38:         }
  39:         return t;
  40:     }
  41: }

Le code ce coup-ci a pas mal changé. Tout d'abord on voit apparaître un dictionnaire qui va contenir tous nos singletons en fonction d'une clé qui sera le paramètre fourni lors de l'appel à GetInstance. On reconnait aussi le ReaderWriterLockSlim qui est là pour éviter les collisions de threads lorsque deux threads concurrents essaieront d'obtenir un même singleton qui n'existe pas encore.

Normalement le code devrait être Thread-Safe, mais j'avoue que je n'ai fait aucune charge pour m'en assurer.

Pour obtenir un singleton paramétré, on fait désormais cet appel :

   1: Singleton<MyClass, int>.GetInstance(1).DoThings(); 
   2: Singleton<MyClass, int>.GetInstance(2).DoThings();

Ces deux lignes utiliseront deux instances distinctes de la classe MyClass.

Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

# re: Singleton générique @ lundi 31 mars 2008 12:26

Ce nouveau verrou ReaderWriterLockSlim est très intéressant, puisque avant pour implémenter ce genre de singleton, il fallait utiliser un "lock" ordinaire, et cela pouvait dégrader les performances (les lectures étaient serialisées).

Je préfère quand même la 1ere solution qui est thread safe "by design".

RaptorXP

# re: Singleton générique @ lundi 31 mars 2008 13:01

Surtout que bon, écrire:

public class Singleton

<T> where T : class, new()

{

 public static readonly T Instance = new T ();

}

ça fait pas trop au poignet :)

Jb Evain

# re: Singleton générique @ lundi 31 mars 2008 14:47

Oui le singleton peut être utile dans certains cas... mais certainnement pas pour accéder à une base de données !

Ca passe quand on teste seul son application par contre au niveau montée en charge (pour une appli web par exemple) c'est fortement déconseillé (voire banni !).

Kangoo

# re: Singleton générique @ lundi 31 mars 2008 14:53

Vous me direz tout ce que vous voulez, c'est plus long que le static (que moi j'appelle d'ailleurs Shared). Enfin, c'est vrai qu'en C# vous n'avez pas la facilité d'écriture que le VB offre avec ses Module's.

Cepedant, toute considération C#&lt;&gt;VB gardée :

- Singleton = invocation inutile d'une fonction et d'un constructeur

- Occupation de mémoire inutile (pointeurs vers l'instance, son type, ...)

- Un static est par définition plus rapide car il ne nécéssite moins de manipulations de la callStack.

- Les génériques étant compilés au démarage de l'appli ou lorsqu'ils sont utilisés, vous perdez du temps précieux...

FREMYCOMPANY

# re: Singleton générique @ lundi 31 mars 2008 15:45

Kangoo &gt; Bonjour, peux-tu développer pourquoi il ne faudrait pas faire de singleton sur une classe qui accède à une base de données stp ? J'ai utilisé cette méthode sur un site qui supporte très bien la charge (environ 30.000 utilisateurs simultanés).

FREMYCOMPANY &gt; Je le concède tout à fait que le singleton générique est plus lent qu'une variable statique. La différence d'exécution dont je voulais parler était celle entre un singleton (statique ou générique) et le fait d'instancier la classe avant chaque appel.

Ce billet n'avait pour but que de montrer un 'raccourci' lorsque l'on a des dizaines de classes sur lesquelles on désire avoir un singleton, et a fortiori un singleton paramétré.

Luke77

# re: Singleton générique @ lundi 31 mars 2008 15:52

@Luke : En effet, instancier la classe à chaque fois est absolument catastrophique niveau perfs, et encore plus niveau perte de temps pour le développeur (code plus long à taper, ...) !

FREMYCOMPANY

# re: Singleton générique @ lundi 31 mars 2008 16:58

Histoire d'avoir un ordre de grandeur, j'ai effectué un petit test rapide, pour 10.000.000 tests :

Sans singleton : 51.30 s

Static : 0.83 s

Générique : 1.36 s

Luke77

# re: Singleton générique @ lundi 31 mars 2008 18:46

Ça alors, instancier dix millions d'objets prend plus de temps que de ne pas le faire !

Moi ce qui me gène le plus cela dit avec cette approche, c'est qu'à la base, le singleton a été fait pour faire en sorte, pour une classe donnée, qu'il ne puisse y avoir qu'une et unique instance.

Et ici on voit bien que ça peut créer des problèmes, le Singleton générique a même une contrainte qui demande que le type que l'on veut Singletoniser ait un constructeur accessible et sans paramètre.

Ce qui veut dire que potentiellement, d'autres gens peuvent très bien instancier cet objet, alors que l'application est construite dans l'idée qu'il ne puisse t'y en avoir qu'un.

Jb Evain

# re: Singleton générique @ lundi 31 mars 2008 20:17

Un Singleton est mieux avec un constructeur privé et on doit pas utiliser ce pattern pour faciliter l'écriture, mais seulement lorsqu'il y a un besoin (par design) d'avoir tjrs la même instance.

Les problèmes dûs aux multithreading sont aussi à considérer.

badrbadr

# re: Singleton générique @ lundi 31 mars 2008 22:43

Et bien si tu colles une connexion à une base dans un singleton et que tu ne te sers que de ton singleton, je vois mal comment 30.000 utilisateurs vont faire pour l'utiliser tous en même temps :)

Pour les accès aux données : tu peux à la limite mettre ta dalle en static (mais pas en singleton) mais certainnement pas ta connexion...

A+

Kangoo

# re: Singleton générique @ mardi 1 avril 2008 10:09

Kangoo : honnêtement je ne comprends pas, ma dal peut très bien être en singleton, tant que ma connexion n'est ni en static ni en variable membre.

Simplement la lecture des informations de configuration de la base de données, ou l'init de certains paramètres ne seront pas à refaire si tu mets ta dal en singleton.

Et je peux t'assurer que ca fonctionne réellement très bien durant les montées en charge.

Jb, badrbadr : attention, je ne dis pas qu'il faut tout mettre en singleton, loin de là. Simplment avec ce générique c'est pour moi un gain de temps dans l'écriture et je n'utilise cette méthode que lorsque l'objet peut être instancier plusieurs fois sans compromettre l'ensemble de l'application (le but étant surtout de ne pas instancier à chaque fois l'objet et non pas d'assurer réellement un singleton).

Lorsqu'il s'agit d'être sûr qu'il n'existe qu'une seule instance, un constructeur private est bien sûr obligatoire.

Luke77

# re: Singleton générique @ mardi 1 avril 2008 16:18

Fremy: "Un static est par définition plus rapide car il ne nécéssite moins de manipulations de la callStack." : je ne comprends pas de quoi tu parles, un singleton EST une variable static.

"Singleton = invocation inutile d'une fonction et d'un constructeur": tu arrives a utiliser une classe sans appeler son constructeur toi ?

Je ne vois pas quelle différence ça fait d'utiliser un singleton ou pas.

Kangoo/Luke: j'utilise aussi un singleton pour ma DAL, il faut juste faire gaffe qu'elle créé bien une nouvelle connection pour chaque requête.

RaptorXP

# re: Singleton générique @ mercredi 2 avril 2008 10:28

Oui je sais utiliser une classe sans l'instancier.

On appelle ca un "Module" ou plus simplement une "classe qui contient des méthodes statiques", le module ayant cela de particulier qu'on ne peut pas créer d'instance de lui, ce qui reste +/- possible avec une classe.

Je n'ai pas parlé d'instance de classe à ce que je sache...

Un singleton est inutile car une classe est elle-même un singleton (en effet, ses membres Shared sont initialisés aux chargement de la classe ou de l'appli (j'ai jamais vraiment su quand). On appelle ca le constructeur de classe, et c'est entièrement threedsafe.

Utiliser plusieurs singleton peut être utile, mais alors ce n'est plus un Singleton ;)

FREMYCOMPANY

# re: Singleton générique @ mardi 22 avril 2008 23:23

Bon alors le code générique peut servir (mais faut revoir quelque truc genre contructeur public)

Par contre ton exemple est completement bizarre (je reste poli), comme on te l'a signalé le singleton est un pattern dont les cas d'utilisation sont bien bornés (et c'est pas de servir la feignantise du développeur).

FREMY : Les modules sont transformés en classe static la premiere utilisation déclenche donc sont initialisation.

Crazyht

crazyht


Les 10 derniers blogs postés

- Dell Inspiron Mini 9 - Enfin en vente !!! par The diary of EBArtSoft le il y a 8 heures et 50 minutes

- Solution Template et Project Template dans Visual Studio par Atteint de JavaScriptite Aiguë [Cyril Durand] le il y a 11 heures et 32 minutes

- PocketIE et Assignation du SRC d'un Element IMG par Jerome Laban le il y a 12 heures et 24 minutes

- Conversion de fichiers RAW en fichier JPEG avec WPF par Perspective le il y a 13 heures et 0 minutes

- Mise à Jour du Moteur de Recherche des Arrêts de Bus de Montréal par Jerome Laban le il y a 13 heures et 44 minutes

- [WPF] XPSReader v0.2 par Blog Technique d'Audrey PETIT le il y a 14 heures et 45 minutes

- Entity Framework : providers Oracle, MySQL et PostgreSQL par Matthieu MEZIL le il y a 21 heures et 20 minutes

- [WPF] Nouvel article sur c2i.fr par Richard Clark le 09-06-2008, 17:33

- F# nouvelle CTP 1.9.6.2 (update) par Pierrick's Blog le 09-06-2008, 13:27

- La suite ...Proposition de collaboration rédactionnelle entre les communautés de développeurs et Microsoft France par LucasR le 09-05-2008, 17:45