Dans la série des articles sur le singleton, je trouvais primordial de discuter de généricité.
Lorsqu’un programme, une application, un module, etc., est développé de façon professionnelle et durable, celui-ci se doit d’avoir une généricité optimale. Celle-ci aura comme résultat de réduire le nombre de ligne de code, de simplifier l’implémentation d’une fonctionnalité et permettra de réutiliser certains concepts dans un autre but, sans avoir à modifier quoi que ce soit. Le singleton ne fait pas exception et peut être gérer au maximum de la généricité.
Prenons un cas concret, le cas d’une application qui doit utiliser de la journalisation (logging). Trois types de journalisations sont disponibles : Journal d’événement, Fichier .txt et Base de données. Chacun de ces types de journalisation héritent de certains comportements du parent, soit l’objet Journalisation.
Notre hiérarchie est donc la suivante :
Sachant que nous ne voulons qu’une seule instance de chacun des types de journalisation, il serait intéressant d’implémenter le singleton dans chacune des classe concrète de journalisation. Nous aurions donc le schéma suivant :
Cette implémentation est bien, mais peut être encore améliorée. Pourquoi ? L’action d’instancier un singleton est toujours la même. Il serait donc intéressant de généraliser le comportement dans la classe parent en lui ajoutant l’instanciation unique d’un type de journalisation. Le schéma obtenu serait donc :
Maintenant toute cette belle réalisation théorique effectuée, il est temps de le coder… et de s’arracher les cheveux de la tête parce qu’on se rend compte bien vite que la création d’objet concret dans un objet abstrait est beaucoup plus complexe qu’on puisse le penser. En détails, voici le problème : dans les technologies .NET, une instanciation doit être explicite, c’est-à-dire qu’on ne peux faire que des nouvelles instances de types connus, ou de types génériques ayant le marqueur d’instanciation. On ne peut donc pas faire une instanciation d’un type enfant générique sans passer par l’utilisation de la réflexion. Dans le cas qui nous intéresse, la classe Journalisation ne peut pas instancier aucun de ses enfants, puisqu’elle ne les connait pas du tout.
Ici-bas, on trouve la classe parent, qui gère l’instanciation du singleton :
public abstract class Journalisation<T> : where T : Journalisation
{
#region Membres prives
/// <summary>
/// Singleton private member
/// </summary>
private T singleton;
#endregion
#region Singleton
/// <summary>
/// Implementation d'un singleton generique de journalisation
/// </summary>
public static T Instance
{
get
{
if (singleton == null)
{
singleton = (T)Activator.CreateInstance(typeof(T));
}
return singleton;
}
}
#endregion
#region Constructeurs
/// <summary>
/// Constructeur prive
/// </summary>
private Journalisation() { }
#endregion
}
Et on trouve la classe enfant, qui se fait instanciée par le comportement hérité de son parent :
public class FichierTxt : Journalisation<FichierTxt>
{
#region Constructeurs
/// <summary>
/// Constructeur prive
/// </summary>
private FichierTxt() : base() { }
#endregion
}
De cette façon, tous les enfants pourront être des enfants de type singleton, sans avoir à gérer le comportement d’instanciation unique d’eux-même.
La généricité de cette solution permet d’isoler les responsabilités et assure une continuité et une facilité dans la maintenance d’une base de code.
Dans le prochain article, on améliorera le concept présent lors de son utilisation dans le contexte d’une requête HTTP.



Le désavantage majeur est que l’application devient difficile à tester. Comment mocker les différents singletons lors de tests?
Il serait probablement plus efficace de générer les singletons aux travers de fabriques (factory) afin de les tester convenablement. Dans ce cas, une version mockée du singleton pourra être utilisée lors des tests. Ça fait du sens ?