Implémentation du Patron de Conception Singleton
Date de publication : 30 Août 2008
		Par
		Eric GASPARD
 
		
			Voyons comment implémenter le Patron de Conception Singleton en Delphi.	
		
	 
		I. Introduction
			
			
			
		
		II. Principes de base
			
			
			
						
		
		III. Implémentation d'un Singleton de type simple
			
			III-A. Variable globale
				
				
			
			III-B. Accesseur
				
				
				
						
		
		IV. Implémentation d'un Singleton-objet
			
			
			IV-A. Variable de classe
				
				
								
			
			IV-B. Accesseur
				
				
			
			IV-C. Libération du Singleton
				
								
				
			
			
			IV-D. Utilisation
				
				
				
			
			IV-E. Faire sans variable de classe
				
				
				
			
		
		V. Aller plus loin
			
			V-A. Constructeurs et destructeurs
				
								
				
				
			
			V-B. Singleton et héritage
				
				
				
								
			
			V-C. Thread-safe
				
				
				
				
				
				
			
		
	
		I. Introduction
			
			
				Dans ce tutoriel nous verrons comment implémenter le Patron de Conception Singleton en Delphi. 
			
			
				Ce tutoriel s'adresse particulièrement aux débutants, aussi nous verrons les fondements du Singleton ainsi que son implémentation pas à pas, notamment celle du Singleton-objet.
			
		
		II. Principes de base
			
			
				Un Singleton représente une ressource commune à tout le programme et qui est unique en mémoire.
			
			
				Un Singleton peut être un type simple ou bien un objet.
			
			
				Nous n'avons pas besoin de l'allouer et de le désallouer,  la Singleton est censé gérer de lui-même son espace mémoire. Ce qui implique qu'un Singleton ne doit pas être créé ou détruit autrement que comme il a été prévu.
			
			
		
		III. Implémentation d'un Singleton de type simple
			
			III-A. Variable globale
				
				
					En Delphi rien de plus facile à faire qu'un Singleton avec un type simple. Pour cela nous pouvons utiliser tout simplement les variables globales. Comme leur nom l'indique, les variables globales représentent un espace mémoire unique et sont accessibles à travers tout le programme pour peu qu'on ajoute le uses qui contient leurs déclarations dans la partie interface.
				
| Exemple d'un Singleton de type entier | 
interface
var
  _singleton : Integer = 12;
  
  | 
 
			
			III-B. Accesseur
				
				
					Nous avons créé notre premier Singleton, l'ennui c'est que tel qu'il est déclaré le programmeur peut éventuellement décider de modifier sa valeur ce que nous ne souhaitons pas forcemment. Aussi nous pouvons décider de donner un accesseur à notre variable globale et déplacer la déclaration de cette dernière dans la partie implémentation de sorte qu'elle ne soit plus visible pour les autres unités du programme.					
				
| Exemple d'un Singleton de type entier avec accesseur | 
interface
function Singleton: Integer;
implementation
var
  _singleton : Integer = 12;
  
function Singleton: Integer;
begin
  Result := _singleton;
end;
  
  | 
 
				  | 
					Notez ici que cet exemple est complètement trivial. En effet on peut tout à fait s'en tirer en déclaration notre variable globale en tant que constante dans la partie interface.
				 | 
						
		
		IV. Implémentation d'un Singleton-objet
			
			
				Maintenant que nous avons vu les fondements d'un Singleton, nous allons pouvoir rentrer dans le vif du sujet en implémentant le comportement Singleton sur un objet.
			
			IV-A. Variable de classe
				
				
					Comme vu précédemment, nous avons besoin d'une variable globale pour contenir notre instance du Singleton. Ce rôle peut être joué par une variable de classe. En effet cette dernière est commune à la classe, elle est donc unique à tout le programme.
				
| Singleton avec variable de classe | 
interface
type
  TMonSingleton = class
  private
   class var
   _Instance: TMonSingleton;
  end;
  
  | 
 
				  | 
					Les variables de classes ne sont apparues qu'à partir de Delphi 2005, nous verrons dans une section plus bas comment faire un Singleton sans y recourrir.
				 | 
				
			
			IV-B. Accesseur
				
				
					Cette variable de classe étant privée, nous allons lui ajouter un accesseur public sous forme de propriété de classe. Comme tout objet, le Singleton a besoin d'être instancié, nous allons donc adjoindre un getter à la propriété qui se chargera d'instancier la variable de classe si ce n'est pas déjà fait.
				
| Singleton avec variable de classe | 
interface
type
  TMonSingleton = class
  private
   class var
   _Instance: TMonSingleton;
   class function GetSingleton: TMonSingleton; static;
  public
   class property Singleton: TMonSingleton read GetSingleton;
  end;
implementation
class function TMonSingleton.GetSingleton: TMonSingleton;
begin
  if not Assigned( _Instance ) then
   _Instance := TMonSingleton.Create;
  Result := _Instance;
end;
  | 
 
			
			IV-C. Libération du Singleton
				
				
					Comme tout objet, l'instance du Singleton a besoin d'être libérée. Ceci ne peut intervenir que lorsque le programme sera en train de se fermer puisque le reste du temps, le Singleton doit tout le temps être accessible.
				
				
				
					Pour ce faire nous allons donc utiliser les sections d'initialization et surtout de finalization de l'unité.
				
| Singleton avec variable de classe | 
interface
type
  TMonSingleton = class
  private
   class var
   _Instance: TMonSingleton;
   class function GetSingleton: TMonSingleton; static;
  public
   class property Singleton: TMonSingleton read GetSingleton;
  end;
implementation
class function TMonSingleton.GetSingleton: TMonSingleton;
begin
  if not Assigned( _Instance ) then
   _Instance := TMonSingleton.Create;
  Result := _Instance;
end;
initialization
finalization
 if Assigned( TMonSingleton._Instance ) then
  TMonSingleton._Instance.Free;
  | 
 
			  | 
				Notez qu'ici nous avons choisi de créer l'instance qu'au niveau du getter ce qui a pour effet que l'objet n'est créé que si le programme ne l'utilise réellement et évite de traîner une variable globale si jamais il ne devait pas s'en servir. Néanmoins si on aurait voulu que l'instance soit créé dès le démarage alors on l'aurait placé le code d'instanciation au niveau de la section d'initialization.
			 | 
			
			IV-D. Utilisation
				
				
					Voila nous avons maintenant implémenté le comportement de Singleton à notre classe. Nous pouvons maintenant ajouter tout les champs, propriétés et méthodes dont nous auront besoin.
				
				
					Et pour l'appeler, rien de plus simple :
				
| Exemple d'utilisation d'un Singleton | 
  [...]
  TMonSingleton.Singleton.MaPropriete;
  [...]
  | 
 
			
			IV-E. Faire sans variable de classe
				
				
					Il est tout à fait possible de faire un Singleton pour un objet sans passer par une variable de classe, c'est d'ailleurs même obligatoire pour les versions inférieures à Delphi 2006.
				
				
					Pour ce faire il suffit de passer par une variable globale comme pour un type simple, ce qui nous donne :
				
| Singleton sans variable de classe | 
interface
type
  TMonSingleton = class
  end;
  
function GetSingleton: TMonSingleton;
implementation
var
  _Instance: TMonSingleton;
  
function GetSingleton: TMonSingleton;
begin
  if not Assigned( _Instance ) then
   _Instance := TMonSingleton.Create;
  Result := _Instance;
end;
initialization
 _Instance = NIL;
finalization
 if Assigned( _Instance ) then
  _Instance.Free;
  | 
 
			
		
		V. Aller plus loin
			
			V-A. Constructeurs et destructeurs
				
				
					Dans la définition du patron de conception pour le Singleton. Il est décrit qu'il faut baisser la visibilité des constructeurs et destructeurs de la classe jusqu'en private de sorte que le programmeur ne puisse pas avoir plus d'une instance du Singleton et ne puisse pas non plus libérer celle existante.
				
				
				
					Ceci n'est pas possible en Delphi car le Create et le Free ne sont pas déclarés en virtual au niveau de TObject. De fait si l'on baisse la visibilité dans notre classe de ces deux méthodes là alors le compilateur considère qu'il s'agit d'une tentative de masquage plutôt qu'une redéfinition mais laisse apparaître celles venant de TObject.
				
				
					Nous pourrions tenter de résoudre ce problème via des méthodes complexes mais le jeu n'en vaut pas la chandelle. Mieux vaut laisser à la discretion du programmeur ces notions, il s'aperçevra bien rapidemment de ses erreurs si jamais il venait à détruire son Singleton avant l'heure par exemple.
				
			
			V-B. Singleton et héritage
				
				
					De la façon dont nous avons implémenté notre Singleton, ce dernier ne fonctionne pas en cas d'héritage de notre classe TSingleton.
				
				
					En effet pour que cela soit valable il faudrait que chaque classe hérité dispose de sa propre variable de classe or actuellement ce n'est pas le cas, la variable de classe est partagée entre toutes les classes héritées (notez que le problème est le même avec des variables globales).
				
				
					Néanmoins il n'a pas été précisé si le Singleton est un comportement hérité ou implémenté ad-hoc. La manière montrée ici s'applique plutôt à une implémentation ad-hoc, dans le cadre d'un héritage il vaut donc mieux l'implémenter que sur les classes finales ou bien utiliser une fabrique de classe qui gèrerait une instance unique par classe héritée plutôt que de s'appuyer sur une variable de classe (ou globale).
				
				
			
			V-C. Thread-safe
				
				
					Dans le cadre d'une gestion multi-thread, il est important de rendre notre code capable de faire façe aux appels simultanés de Threads.
				
				
					Pour ce faire nous avons ajoutés deux nouvelles méthodes Lock et Unlock qui vont utiliser une section critique pour bloquer les accès concurrents au Singleton. Chaque utilisation d'un Singleton dans le programme doit être entouré d'un appel à Lock puis Unlock.
				
				
					Tant que Unlock n'aura pas été appelé, les autres threads seront bloqués et ne pourront pas continuer leur exécution. Il est donc primordial que chaque appel de Lock ait son équivalant Unlock.
				
| Singleton thread-safe | 
interface
type
  TMonSingleton = class
  private
   class var
   _Instance: TMonSingleton;
   class function GetSingleton: TMonSingleton; static;
  public
   class procedure Lock;
   class procedure Unlock;
   class property Singleton: TMonSingleton read GetSingleton;
  end;
implementation
uses SyncObjs;
var
  SingletonLock: TCriticalSection;
class function TMonSingleton.GetSingleton: TMonSingleton;
begin
  if not Assigned( _Instance ) then
   _Instance := TMonSingleton.Create;
  Result := _Instance;
end;
class procedure TMonSingleton.Lock;
begin
  SingletonLock.Acquire;
end;
class procedure TMonSingleton.Unlock;
begin
  SingletonLock.Release;
end;
initialization
  SingletonLock := TCriticalSection.Create;
finalization
  if Assigned( TMonSingleton._Instance ) then
   TMonSingleton._Instance.Free;
  SingletonLock.Free;
  | 
 
				
					Et par exemple pour un appel :
				
| Exemple d'utilisation d'un Singleton thread-safe | 
  TMonSingleton.Lock;
  try
   [...]
   TMonSingleton.Singleton.MaPropriete;
   [...]
	
  finally
   TMonSingleton.Unlock;
  end;
  | 
 
				  | 
					Pour la version sans variable de classe on peut très bien transformer les deux méthodes de classe Lock et Unlock en de simples routines.
				 | 
			
		
	


 
Les sources présentées sur cette page sont libres de droits
et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation
constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ©
2008 Eric GASPARD. Aucune reproduction, même partielle, ne peut être
faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc.
sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à
trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.