Implémententation du Singleton avec Delphi 7
Un tutoriel de Jérémy Laurent
Le 2015-01-28 21:00:10, par Alcatîz, Responsable Pascal, Lazarus et Assembleur
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 ?
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.
-
AndnotorRédacteur/ModérateurNon, il n'y a pas d'instance, c'est directement la déclaration de la classe qui est altérée.
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é.
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 : 1
2
3
4
5function TDemo.func2: string; begin v1 := integer(ClassType) ; result := intToHex (V1,8) end;
le 30/01/2015 à 19:05 -
Paul TOTHExpert éminent séniorhaha
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 : 1
2
3
4
5
6type TSingleton = class() public MaVar: Integer; end;
Code : 1
2
3
4
5
6
7
8
9
10interface // équivalent du public function GetVar: Integer; procedure SetVar(Value: Integer); implementation // équivalent du private var MaVar: Integer;
le 05/02/2015 à 16:23 -
ShaiLeTrollExpert éminent séniorA 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... !le 02/02/2015 à 10:10 -
Paul TOTHExpert éminent séniorallez, 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 versionsCode : 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
27unit 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.
Code : 1
2
3
4
5
6
7
8
9uses MySingleton; begin MySingleton.PublicValue := 1; MySingleton.GetPrivateValie; MySingleton.MaMethod(); end;
le 02/02/2015 à 14:38 -
AndnotorRédacteur/ModérateurOui mais c'est faux de parler d'instance. Une instance est une allocation dynamique en fonction d'une déclaration.
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 : 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17type 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;
Ajoutons maintenant une variable qui elle devrait être allouée (donc y avoir un Create) :
Code : 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18type 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;
Peut-on tester s'il y a bien eu allocation ? Oui, par Self :Code : 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19type 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;
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.
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 constructeurle 31/01/2015 à 16:20 -
ShaiLeTrollExpert éminent séniorMerci à 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'autreCode : 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
48type 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;
le 09/04/2021 à 14:15 -
AndnotorRédacteur/ModérateurCe 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 : 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
36type 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;
le 31/01/2015 à 16:23 -
popoExpert confirmé
Envoyé par Andnotor
Envoyé par Andnotor Code : 1
2
3Ptr := FInstance; Destroy; Self := Ptr;
le 01/02/2015 à 19:02 -
ShaiLeTrollExpert éminent séniorInté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'interfacele 02/02/2015 à 14:14 -
Paul TOTHExpert éminent séniorLOL, tu as toujours fait de l'objet ? car c'est juste de la déclaration de fonction classiquesle 05/02/2015 à 21:15