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

Vous êtes nouveau sur Developpez.com ? Créez votre compte ou connectez-vous afin de pouvoir participer !

Vous devez avoir un compte Developpez.com et être connecté pour pouvoir participer aux discussions.

Vous n'avez pas encore de compte Developpez.com ? Créez-en un en quelques instants, c'est entièrement gratuit !

Si vous disposez déjà d'un compte et qu'il est bien activé, connectez-vous à l'aide du formulaire ci-dessous.

Identifiez-vous
Identifiant
Mot de passe
Mot de passe oublié ?
Créer un compte

L'inscription est gratuite et ne vous prendra que quelques instants !

Je m'inscris !

Implémententation du Singleton avec Delphi 7
Un tutoriel de Jérémy Laurent

Le , par Alcatîz

6PARTAGES

7  0 
Implémentation du Singleton avec Delphi 7, par Jérémy Laurent
Premier article d'une série consacrée aux patrons de conception (design patterns)

Jérémy Laurent a décidé de présenter l'implémentation de différents patrons de conception (design patterns) avec Delphi. Le premier d'entre eux est le « Singleton », qui est un modèle visant à limiter l'instanciation d'une classe à un seul et unique objet. Il est couramment utilisé pour coordonner des opérations dans un système.

Cette implémentation ne pose aucun problème sur les dernières versions de Delphi ; par contre, avec les versions plus anciennes, les champs statiques ne sont pas autorisés et provoquent une erreur de compilation. Cet article présente différentes solutions.

http://jeremy-laurent.developpez.com...rns/singleton/

Qu'en pensez-vous ?

Une erreur dans cette actualité ? Signalez-nous-la !

Avatar de Andnotor
Rédacteur/Modérateur https://www.developpez.com
Le 30/01/2015 à 19:05
Citation Envoyé par ALWEBER Voir le message
Lorsque l'objet défini contient des Class Var, Class Property, Class Function, etc. une instance unique de l'objet est automatiquement créée au lancement du programme.
Non, il n'y a pas d'instance, c'est directement la déclaration de la classe qui est altérée.

Citation Envoyé par ALWEBER Voir le message
Même si les Class éléments peuvent cohabiter avec d'autres élément dans la description d'une classe, les Class éléments ne peuvent pas communiquer avec les autres éléments (et réciproquement).
Le réciproquement est faux. L'instance sait où se trouve la déclaration de la classe. Tu l'illustres d'ailleurs dans func1, fv2 est bien incrémenté.

Citation Envoyé par ALWEBER Voir le message
En réalité Delphi travaille sur deux instances différentes de classes...
Non

Ton code n'est pas bon dans le sens où tu te réfères toujours à Self mais Self est l'instance dans un cas et la classe dans l'autre. Ajoute cette autre fonction...

Code : Sélectionner tout
1
2
3
4
5
function TDemo.func2: string;
begin
  v1 := integer(ClassType) ;
  result := intToHex (V1,8)
end;
...et tu verras qu'elle renvoie bien le même pointeur que classfunc2.
6  0 
Avatar de Paul TOTH
Expert éminent sénior https://www.developpez.com
Le 05/02/2015 à 16:23
Citation Envoyé par popo Voir le message
Bonjour Paul et merci pour ta contribution.
D'habitude, lorsque je lis un de tes posts, soit je suis d'accord, soit j'apprends quelque chose (cas qui arrive le plus souvent et je t'en remercie. ).
Mais dans ce cas précis, à moins d'avoir loupé quelque chose, j'ai du mal à voir le singleton :
- Il n'y a pas de classe.
- Si je remplace l'entier par une classe dans ton exemple, rien ne m'empêche de faire un Free sur PublicValue
- Toujours en remplaçant l'entier par une classe, rien ne m'empêche non plus de créer plusieurs instance puisque la déclaration reste publique.
haha mais c'est l'unité qui est un singleton à elle seule il est impossible de l'instancier deux fois dans le projet, il est même impossible de la désallouer, justement car ce n'est pas un objet

bon ok, tu ne peux pas dériver d'une classe de base (encore que tu peux très bien appeler une unité CustomSingleton.pas qui déclarerait une function MaMethod)...mais pas de méthodes dynamique (sauf à ajouter des pointeurs de fonctions...mais bon...) et surtout pas de property.

en fait ce que je voulais surtout souligner c'est que Delphi n'est pas un langage 100% objet, et je ne vois aucun mal à utiliser des variables et fonctions globales qui présentent le gros avantage d'avoir implicitement un espace de nom lié à leur unité : "NomUnit.NomFonction" qui dans l'IDE se comportant très exactement comme "Instance.Methode"

Et pour les points 2 et 3 de ta remarque, c'est tout aussi vrai d'un singleton déclaré comme ceci:
Code : Sélectionner tout
1
2
3
4
5
6
type
  TSingleton = class()
  public
    MaVar: Integer;
  end;
la différence est la possibilité d'utiliser une "property" alors qu'avec une approche non objet tu auras au mieux des getter et setter.

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
interface // équivalent du public

function GetVar: Integer;
procedure SetVar(Value: Integer);

implementation // équivalent du private

var
  MaVar: Integer;
5  0 
Avatar de ShaiLeTroll
Expert éminent sénior https://www.developpez.com
Le 02/02/2015 à 10:10
A la base le Tutoriel concerne "Singleton dans une version de Delphi qui ne supporte pas les champs statiques" comme l'est Delphi 7 !

Evoquer XE est hors sujet comme le souligne AndNotOr !
Avec le class var, créer un Singleton peut se faire comme en Java ou C#, ce n'est pas un grand défi !

Sous Delphi 7, ce n'est pas difficile mais demande un peu plus d'astuce comme le démontre parfaitement le tutoriel de Popo

D'ailleurs, le tutoriel évoque NewInstance pour gérer la création d'instance !

Il existe plein de générique pour créer un Singleton en exemple sur le net,
dont le mien [post=7517854]Design Pattern : Singleton - Découverte des Génériques et Class Constructor [/post] et ses classes TSLTSingleton et TSLTSingletonThreadSafe

Ainsi pas besoin de réécrire le même code de gestion du singleton à chaque fois !
Obligatoire sous D7 à cause de l'ancienneté de la version mais on peut faire mieux sous XE... !
4  0 
Avatar de Paul TOTH
Expert éminent sénior https://www.developpez.com
Le 02/02/2015 à 14:38
allez, j'ajoute mes 2 cents, si on veux un vrai singleton il faudrait aussi une classe figée sinon rien ne m'empêche de surcharger ce qui est mis en place et sealed n'est pas supporté sous D7 si mon souvenir est bon.

après moi j'aime bien cette écriture du singleton qui fonction avec toutes les versions

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
unit MySingleton;

interface

var
  PublicValue: Integer;

procedure MaMethod;

function GetPrivateValue: Integer;

implementation

var
  PrivateValue: Integer;

function GetPrivateValue: Integer;
begin
  Result := PrivateValue;
end;

procedure MaMethod;
begin
end;

end.
et ça s'utilise comme ceci:

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
uses
  MySingleton;

begin
  MySingleton.PublicValue := 1;
  MySingleton.GetPrivateValie;
  MySingleton.MaMethod();
end;
4  0 
Avatar de Andnotor
Rédacteur/Modérateur https://www.developpez.com
Le 31/01/2015 à 16:20
Citation Envoyé par ALWEBER Voir le message
Remarque sur la notion d'instance : J'ai choisi d'utiliser la notion d'instance plutôt que 'déclaration de la classe' car dans le cas qui est présenté la variable fV2 est bien localisée dans cet espace mémoire
Oui mais c'est faux de parler d'instance. Une instance est une allocation dynamique en fonction d'une déclaration.

Citation Envoyé par ALWEBER Voir le message
Une première instance de classe est automatiquement créée au lancement du programme et sert de déclaration de la classe.
Et comment cela serait-il possible ? Imagine simplement que le constructeur attende un certain nombre de paramètres, comment ceux-ci pourraient-ils être évalués ?
La déclaration de la classe est en mémoire, oui ! Mais c'est le programme après tout ! Pour illustrer cela, je te propose une autre petit démo :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
type
  TDemo = class
    procedure Test;
  end;

var
  Demo :TDemo = nil;

procedure TDemo.Test;
begin
  ShowMessage('Texte');
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Demo.Test;
end;
Il n'y a aucune instance de TDemo qui est créée, mais ça fonctionne ! Le code est là et exécutable et pourtant il n'y a pas de class var, class function, etc.

Ajoutons maintenant une variable qui elle devrait être allouée (donc y avoir un Create) :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type
  TDemo = class
    Value :string;
    procedure Test;
  end;

var
  Demo :TDemo = nil;

procedure TDemo.Test;
begin
  ShowMessage(Value);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Demo.Test;
end;
Nous obtenons une belle violation d'accès. Normal !
Peut-on tester s'il y a bien eu allocation ? Oui, par Self :

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
type
  TDemo = class
    Value :string;
    procedure Test;
  end;

var
  Demo :TDemo = nil;

procedure TDemo.Test;
begin
  if Self <> nil then
    ShowMessage(Value);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Demo.Test;
end;
Pas de ShowMessage, mais plus de VA

En bref le code est toujours là et exécutable et ce n'est que Self (un pointeur sur les données propres à cette instance) qui est déterminant.

Citation Envoyé par ALWEBER Voir le message
Lorsque l'objet défini contient des Class Var, Class Property, Class Function, etc. L'accès à l'un de ces Class éléments ce fait en préfixant l'élement par le type de la classe.
A nouveau c'est faux et à nouveau tu prouves le contraire dans ton exemple en appelant une fois TDemo.classfunc2 et l'autre aDemo.classfunc2. Mais puisque c'est une méthode de classe Self représente ici toujours la classe et non l'instance aDemo. La seule méthode de classe où Self représente l'instance est... le constructeur
2  0 
Avatar de ShaiLeTroll
Expert éminent sénior https://www.developpez.com
Le 09/04/2021 à 14:15
Merci à la modération d'avoir déplacé ce message depuis Le patron de conception "Interpréteur" avec Delphi 7, un tutoriel de Jérémy Laurent


Et que montre ce PDF ?
Je ne vois pas de démonstration d'une instance fantôme, juste qu'il y a des "Champs de classe" et de l'autres les "champs d'instance";
Evoquer une instance pour le stockage des "Champs de classe" est très maladroit et une erreur de première dans un cours.

Et cela ne démontre aucunement une erreur sur le Singleton désigné plus haut, juste un choix d'avoir une instance unique au lieu d'utiliser uniquement des "Champs de classe".
Avoir une instance d'ailleurs permet de passer à un Multiton (+Factory) de façon beaucoup plus transparente

Sinon comme démontrer cette théorie de l'instance fantome avec l'héritage

M est partagé par TDemo et TDemo2, l'un modifie l'autre

Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
type
  TDemo = class
  private
    class var M: Integer;
  public
    class function IncM(): string;
  end;

  TDemo2 = class(TDemo)
  private
    class var K: Integer;
  public
    class function IncK(): string;
  end;



{ TDemo }

class function TDemo.IncM(): string;
begin
  Inc(M);
  Result := M.ToString();
end;

{ TDemo2 }

class function TDemo2.IncK(): string;
begin
  Inc(M);
  Inc(K);
  Result := K.ToString();
end;

procedure TForm1.Button5Click(Sender: TObject);
begin
  Memo.Lines.Add('M: ' + TDemo.IncM());
  Memo.Lines.Add('M: ' + TDemo.IncM());
  Memo.Lines.Add('M: ' + TDemo.IncM());

  Memo.Lines.Add('M: ' + TDemo2.IncM());
  Memo.Lines.Add('K: ' + TDemo2.IncK());

  Memo.Lines.Add('M: ' + TDemo2.IncM());
  Memo.Lines.Add('K: ' + TDemo2.IncK());

  Memo.Lines.Add('M: ' + TDemo2.IncM());
end;
2  0 
Avatar de Andnotor
Rédacteur/Modérateur https://www.developpez.com
Le 31/01/2015 à 16:23
Citation Envoyé par ALWEBER Voir le message
Le générateur de Pattern dans Delphi XE pour le Singleton conduit maintenant à ce code :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
  TSingleton = class
  strict private
    class var FInstance: TSingleton;
    constructor Create;
  public
    class function GetInstance: TSingleton;
  end;
pour prévenir d'une éventuelle surcharge du constructor
Ce code n'empêche absolument pas un TSingleton.Create mais c'est TObject.Create qui sera appelé uniquement. Quant à la surcharge, et bien c'est aussi TObject.Create qui le sera ! Il n'est pas possible de réduire la visibilité/portée d'une méthode/propriété.

Quant au singleton, je me demande si on ne pourrait pas simplement l'écrire ainsi (et pardon à Jérémy d'avoir un peu dévié du sujet) :
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
type
  TSingleton = class
  strict private
    class var FInstance: TSingleton;
  public
    class function Get: TSingleton;
    constructor    Create;
    destructor     Destroy; override;
  end;

constructor TSingleton.Create;
var
  Ptr :pointer;
begin
  if Assigned(FInstance) then
  begin
    //Libère l'objet en cours de création et
    //le remplace par l'instance existante
    Ptr := FInstance;
    Destroy;
    Self := Ptr;
  end;

  FInstance := Self;
end;

destructor TSingleton.Destroy;
begin
  FInstance := nil;
  inherited;
end;

class function TSingleton.Get: TSingleton;
begin
  Result := Create;
end;
ainsi appeler plusieurs fois le Create renverrait aussi l'instance existante. Après quelques essais, ça semble être le cas (et aucun Memory leak détecté).
1  0 
Avatar de popo
Expert confirmé https://www.developpez.com
Le 01/02/2015 à 19:02
Citation Envoyé par Andnotor
Quant au singleton, je me demande si on ne pourrait pas simplement l'écrire ainsi (et pardon à Jérémy d'avoir un peu dévié du sujet)
Il n'y a pas de mal.
Citation Envoyé par Andnotor

:
Code : Sélectionner tout
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
type
  TSingleton = class
  strict private
    class var FInstance: TSingleton;
  public
    class function Get: TSingleton;
    constructor    Create;
    destructor     Destroy; override;
  end;
 
constructor TSingleton.Create;
var
  Ptr :pointer;
begin
  if Assigned(FInstance) then
  begin
    //Libère l'objet en cours de création et
    //le remplace par l'instance existante
    Ptr := FInstance;
    Destroy;
    Self := Ptr;
  end;
 
  FInstance := Self;
end;
 
destructor TSingleton.Destroy;
begin
  FInstance := nil;
  inherited;
end;
 
class function TSingleton.Get: TSingleton;
begin
  Result := Create;
end;
ainsi appeler plusieurs fois le Create renverrait aussi l'instance existante. Après quelques essais, ça semble être le cas (et aucun Memory leak détecté).
Je n'ai pas encore eu l'occasion de travailler avec XE. Si j'en ai le loisir, j'essaierai.

Code : Sélectionner tout
1
2
3
Ptr := FInstance;
Destroy;
Self := Ptr;
C'est assez bourrin comme méthode !
1  0 
Avatar de ShaiLeTroll
Expert éminent sénior https://www.developpez.com
Le 02/02/2015 à 14:14
Intéressant l'article sur l'EDN
C'est ni plus ni moins l'approche du TInterfacedObject mais gérer au niveau de la classe au lieu de l'instance via les variables globales
le principal inconvénient de cette approche, c'est qu'il faut libérer explicitement le singleton après chaque utilisation pour faire baisser le compteur de référence
il manque le ThreadSafe avec l'InterlockIncrement

C'est la technique qu'exploite indirectement via une Factory (le Provider) dans la variante avancée à base de ISingleton où le compteur de référence est intégré au TInterfacedObject
C'est le Provider qui force une vie "éternelle" en conservant une référence sur l'interface
1  0 
Avatar de Paul TOTH
Expert éminent sénior https://www.developpez.com
Le 05/02/2015 à 21:15
Citation Envoyé par popo Voir le message
C'est un concept que je vais avoir du mal à assimiler
LOL, tu as toujours fait de l'objet ? car c'est juste de la déclaration de fonction classiques
1  0