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.