IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

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.

               Version hors-ligne (Miroir)

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;
  
warning 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;
  
warning 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

{ TMonSingleton }

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

{ TMonSingleton }

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;
idea 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;

{ TMonSingleton }

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;
idea 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.


               Version hors-ligne (Miroir)

Valid XHTML 1.1!Valid CSS!

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.