Bonjour à tous,
Pour parfaire mes connaissances, j'ai décidé d'explorer l'utilisation des Interfaces. Je n'utilises quasiment jamais les interfaces dans mes développements. Afin de mieux comprendre le fonctionnement de celles-ci, j'ai fait quelques recherches sur le web et notamment sur les "Design Patterns" (sujet qui ne m'est pas inconnu. J'en ai déjà implanté un certain nombre dans mon framework en PHP.
Mais quid de Delphi ? Je suis donc partie sur de petits exercices simples en commençant par le patron"Observer" (C'est sur ce sujet pour lequel j'ai trouvés le plus d'informations pertinentes sur les "Design patterns" avec Delphi).
Je tiens à préciser que je suis conscient que Delphi utilise déja certain de ces modèles de conception.
Bref tout avait bien commencé dans le meilleur des monde. J'ai créé mes interfaces de base (certain commentaires sont des questions, que je me pose):
A la suite j'ai créé deux classes de base
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 Const IBZOBSERVER_GUID = '{6728156B-AF75-4AD6-89DD-9E66F915305F}'; IIBZOBSERVER_GUID : TGUID = IBZOBSERVER_GUID; IBZOBSERVABLE_GUID = '{CBA3A188-CCBF-43B2-A877-F9C47F3B4D67}'; IIBZOBSERVABLE_GUID : TGUID = IBZOBSERVABLE_GUID; IBZOBSERVERSFACTORY_GUID = '{BC5F2D51-8505-4089-86DC-3752C3F20C3B}'; IIBZOBSERVERSFACTORY_GUID : TGUID = IBZOBSERVER_GUID; Type IBZObservable = interface; { IBZObserver : Interface Observateur } IBZObserver = interface [IBZOBSERVER_GUID] procedure ExecuteObserver(Observable: IBZObservable); // Execute une tâche procedure AttachToObservable(Observable: IBZObservable); // Attache l'observateur à un sujet end; { IBZObservable : Interface d'un objet, d'un sujet observable par un observateur } IBZObservable = interface [IBZOBSERVABLE_GUID] procedure AddObserver(Obs: IBZObserver); // Attache un nouvel observateur procedure RemoveObserver(Obs: IBZObserver); // Supprime un observateur procedure NotifyObservers; // Notifie tous les observateurs end; { IBZObserversFactory : Est-ce vraiment nécessaire d'avoir une interface de ce type, pour la fabrique ? } IBZObserversFactory = interface [IBZOBSERVERSFACTORY_GUID] function CreateObserverObject : IBZObserver; function CreateObservableObject : IBZObservable; end;
Jusque là pas de problèmes. Pour tester j'ai réalisé quelques programmes "bateau" dont le but est de réaliser une simple opération mathématique.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62 Type { TBZCustomObservableObject : Objet implémentant l'interface IBZObservable. Cet objet est à surcharger pour pouvoir l'utiliser à des fins précises. Cet objet sert de containaire pour les données ou d'états qui pourra ainsi notifier les changements de valeur à un ou plusieurs observateurs. } TBZCustomObservableObject = class(TInterfacedObject, IBZObservable) private FObservers: TInterfaceList; // Liste des observateurs FObservableRef: IBZObservable; // sert de référence est ce utile ici ????? FOwner : TObject; FTag : Integer; // Propriété fourre-tout : Tag, TagPointer, TagObjet, TagFloat... Tag tag tag.... public { Création de l'objet observable } constructor Create(Observable: IBZObservable); overload; constructor Create(AOwner : TObject; Observable: IBZObservable); overload; { Destruction de l'objet observable } destructor destroy; override; { AddObserver : Attache un nouvel observateur } procedure AddObserver(Obs: IBZObserver); { RemoveObserver : Supprime un observateur } procedure RemoveObserver(Obs: IBZObserver); { NotifyObservers : Notifie tous les observateurs } procedure NotifyObservers; { Tag : Propriété fourre-tout } property Tag : Integer read FTag write FTag; { Propriétaire de l'objet } property Owner : TObject read FOwner write FOwner; end; TBZInterfacedObservableClass = class of TBZCustomObservableObject ; { TBZCustomObserverObject : Objet observateur Implémentant l'interface IBZObserver C'est le sujet observé qui lui notifie lorsque ses proriétés ou son état sont modifiés. Cet objet est à surcharger pour pouvoir l'utiliser à des fins précises. Cependant il peut se suffir à lui même grâce à son événement OnExecute. Si ce dernier est renseigné alors OnExecute est appelé chaque fois que l'observateur doit effectuer une action. } TBZObserverExecuteEvent = procedure (sender: TObject; anObservable: IBZObservable) of object; TBZCustomObserverObject = class(TInterfacedObject, IBZObserver) private FOwner : TObject; FOnExecute : TBZObserverExecuteEvent; // On peut s'en servir dynamiquement protected FObservableRef : TInterfaceList; //IBZObservable; // sert de référence FObservableRefCount : Integer; { ExecuteObserver : Hérité de l'interface IBZObserver } procedure ExecuteObserver(Observable: IBZObservable); virtual; public { Création de l'objet observateur } Constructor Create(AOwner : TObject); { Destruction de l'objet observateur } Destructor Destroy; override; { Attache l'observateur à un objet observable } procedure AttachToObservable(Observable: IBZObservable); virtual; function GetObservableRefCount : Integer; { OnExecute : Evénement appelé à chaque notification faite par un objet observable avec lequel l'observateur est lié. } property OnExecute : TBZObserverExecuteEvent read FOnExecute write FOnExecute; { Propriétaire de l'objet } property Owner : TObject read FOwner write FOwner; end;
Les deux premiers programmes contiennent 2 TEdit pour entrer des valeurs et 1 TLabel qui contient le résultat de la somme des 2 valeurs.
Pour ce faire j'ai créé un nouvel objet Observable enfant
Les valeurs de cet objet observable sont misent à jour au travers les événements OnChange des TEdit
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13 type { TBZObservableValue } TBZObservableValues = class(TBZCustomObservableObject) private FValue1 : Integer; FValue2 : Integer; procedure SetValue1(aValue : Integer); procedure SetValue2(aValue : Integer); public property Value1 : Integer read FValue2 write SetValue1; property Value2 : Integer read FValue1 write SetValue2; end;
Dans mon premier exemple et test (que j'appel les machines) , j'utilise directement "TBZCustomObserverObject" comme observateur et je passe par l'événement OnExecute pour gérer les notifications
Jusque la tout fonctionne. Mais j'ai un doute sur l'utilisation du AS
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 procedure TBZObservableValues.SetValue1(aValue: Integer); begin if FValue1 = aValue then exit; FValue1 := aValue; NotifyObservers; end; procedure TBZObservableValues.SetValue2(aValue: Integer); begin if FValue2 = aValue then exit; FValue2 := aValue; NotifyObservers; end; procedure TForm1.Edit1Change(Sender: TObject); begin FObservableValues.Value1 := StrToInt(Edit1.Text); end; procedure TForm1.Edit2Change(Sender: TObject); begin FObservableValues.Value2 := StrToInt(Edit2.Text); end; procedure TForm1.FormCreate(Sender: TObject); begin FObservableValues := TBZObservableValues.Create(nil); FObserverValue := TBZObserverValue.Create(nil); FObserverValue.AttachToObservable(FObservableValues); FObserverValue.OnExecute := ObserverExecute; end; procedure TForm1.FormDestroy(Sender: TObject); begin FObservableValues.RemoveObserver(FObserverValue); end; procedure TForm1.ObserverExecute(sender: TObject; anObservable: IBZObservable); var LObservable : TBZObservableValues; begin LObservable := anObservable As TBZObservableValues; lblResultat.Caption := IntToStr(LObservable.Value1 + LObservable.Value2); end;
Deuxième exemple au lieu d'employé un objet observateur, je donne la possibilité à ma fiche d'être cet observateur
et je décide également de ne plus passer par l'événement "OnExecute"
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 type TForm1 = class(TForm, IBZObserver) Panel1: TPanel; lblResultat: TLabel; Edit1: TEdit; Edit2: TEdit; ComboBox1: TComboBox; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Edit1Change(Sender: TObject); procedure Edit2Change(Sender: TObject); procedure ComboBox1Change(Sender: TObject); private { Déclarations privées } FObservableObject : IBZObservable; protected { Déclarations protégées } procedure ExecuteObserver(Observable: IBZObservable); public { Déclarations publiques } procedure AttachToObservable(Observable: IBZObservable); end;
Là également tout va bien.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74 procedure TForm1.AttachToObservable(Observable: IBZObservable); begin Observable.AddObserver(Self); end; procedure TForm1.ComboBox1Change(Sender: TObject); begin (FObservableObject as TBZMachineObservableValues).Operation := TMathOperations(Combobox1.ItemIndex); end; procedure TForm1.Edit1Change(Sender: TObject); begin (FObservableObject as TBZMachineObservableValues).Value1 := StrToInt(Edit1.Text); end; procedure TForm1.Edit2Change(Sender: TObject); begin (FObservableObject as TBZMachineObservableValues).Value2 := StrToInt(Edit2.Text); end; procedure TForm1.ExecuteObserver(Observable: IBZObservable); Var LObservableObject : TBZMachineObservableValues; OpResult : Integer; LOp : TMathOperations; LOpStr : String; LVal1, LVal2 : Integer; begin LObservableObject := Observable As TBZMachineObservableValues; LVal1 := LObservableObject.Value1; LVal2 := LObservableObject.Value2; LOp := LObservableObject.Operation; Case LOp of opAdd : begin OpResult := LVal1 + LVal2; LOpStr := ' + '; end; opSub : begin OpResult := LVal1 - LVal2; LOpStr := ' - '; end; opMul : begin OpResult := LVal1 * LVal2; LOpStr := ' * '; end; opDiv : begin LOpStr := ' / '; if LVal2 = 0 then LVal2:=1; OpResult := LVal1 div LVal2; end; end; lblResultat.Caption := LVal1.ToString + LOpStr + LVal2.ToString + ' = ' + OpResult.ToString; end; procedure TForm1.FormCreate(Sender: TObject); begin Try FObservableObject := TBZMachineObservableValues.Create(nil); finally AttachToObservable(FObservableObject); end; end; procedure TForm1.FormDestroy(Sender: TObject); begin FObservableObject.RemoveObserver(Self); end;
Pour mon troisième test, je désire observé le résultat de 2 "Machines" et renvoyé la somme de ces deux résultats
La aussi tout est ok avec la mise en place de trois objets observable, d'un seul observateur par machine. Et je conserve l'observateur via ma fiche pour le résultat final
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12 TBZObserverValue = Class(TBZCustomObserverObject) protected procedure ExecuteObserver(Observable: IBZObservable); override; public // Pour afficher les résultats des machine A et B Lbl1 : TLabel; Lbl2 : TLabel; // Pour afficher le résultat de A + B Lbl3 : TLabel; observable3 : TBZObservableValues; // Contient le résultat des machine A et B End;;
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72 procedure TForm1.FormCreate(Sender: TObject); begin FObservableValues1 := TBZObservableValues.Create(nil); FObservableValues1.Tag := 1; FObservableValues2 := TBZObservableValues.Create(nil); FObservableValues2.Tag := 2; FObservableValues3 := TBZObservableValues.Create(nil); FObservableValues3.Tag := 3; FObserverValue1 := TBZObserverValue.Create(nil); FObserverValue1.Lbl1 := lblResultat1; FObserverValue1.Lbl2 := lblResultat2; FObserverValue1.Lbl3 := lblResultat; FObserverValue1.Observable3 := FObservableValues3; FObserverValue1.AttachToObservable(FObservableValues1); FObserverValue1.AttachToObservable(FObservableValues2); FObserverValue1.AttachToObservable(FObservableValues3); end; procedure TForm1.ObserverExecute(sender: TObject; anObservable: IBZObservable); var LObservable : TBZObservableValues; LVal1, LVal2 : Integer; begin LObservable := anObservable As TBZObservableValues; LVal1 := LObservable.Value1; LVal2 := LObservable.Value2; if (LObservable.Tag = 1) then begin lblResultat1.Caption := IntToStr(LVal1 + LVal2); FObservableValues3.Value1 := LVal1 + LVal2; end else if (LObservable.Tag = 2) then begin lblResultat2.Caption := IntToStr(LVal1 + LVal2); FObservableValues3.Value2 := LVal1 + LVal2; end else begin lblResultat.Caption := IntToStr(LVal1) +' + '+ IntToStr(LVal2) + ' = ' + IntToStr(LVal1 + LVal2); end; end; { TBZObserverValue } procedure TBZObserverValue.ExecuteObserver(Observable: IBZObservable); var LObservable : TBZObservableValues; LVal1, LVal2 : Integer; begin LObservable := Observable As TBZObservableValues; LVal1 := LObservable.Value1; LVal2 := LObservable.Value2; if (LObservable.Tag = 1) then begin lbl1.Caption := IntToStr(LVal1 + LVal2); Observable3.Value1 := LVal1 + LVal2; end else if (LObservable.Tag = 2) then begin lbl2.Caption := IntToStr(LVal1 + LVal2); Observable3.Value2 := LVal1 + LVal2; end else begin lbl3.Caption := IntToStr(LVal1) +' + '+ IntToStr(LVal2) + ' = ' + IntToStr(LVal1 + LVal2); end; inherited end;
Là ou cela se gatte c'est pour mon quatrième test
Au lieu de c'ajouter tous les objets directement à ma fiche, j'ai voulu créer un objet décrivant une machine et ai voulu l'étendre grâce à un autre interface
La fiche :
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 IBZObservableMachineResult = interface ['{B04654A9-798F-40E1-BE72-FCC4938978AB}'] procedure SetOpResult(AValue: Integer); end; TBZCustomMachine = class(TBZCustomObservableObject, IBZObservableMachineResult) private FObservableValues : IBZObservable; //TBZMachineObservableValues; FObserverValues : IBZObserver; //TBZMachineObserverValues; FOpResult : Integer; function GetObservableValues: TBZMachineObservableValues; function GetObserverValues: TBZMachineObserverValues; procedure SetObservableValues(AValue: TBZMachineObservableValues); procedure SetObserverValues(AValue: TBZMachineObserverValues); procedure SetOpResult(AValue: Integer); protected procedure DoEditingDoneEdit1(Sender: TObject); procedure DoEditingDoneEdit2(Sender: TObject); procedure DoComboboxChange(Sender: TObject); public constructor Create(AOwner : TObject; AnID : Integer); overload; destructor Destroy; override; procedure AttachControl(AEdit1, AEdit2 : TEdit; AOpCombobox : TComboBox); property ObservableValues : TBZMachineObservableValues read GetObservableValues write SetObservableValues; property ObserverValues : TBZMachineObserverValues read GetObserverValues write SetObserverValues; property OpResult : Integer read FOpResult write SetOpResult; property Tag; end; TBZMachine = TBZCustomMachine;
En plus j'ai voulu rajouter des "Fabrique"
Code : Sélectionner tout - Visualiser dans une fenêtre à part
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 type TForm1 = class(TForm, IBZObserver) Panel1: TPanel; lblResultat1: TLabel; Edit1: TEdit; Edit2: TEdit; ComboBox1: TComboBox; Panel2: TPanel; lblResultat2: TLabel; Edit3: TEdit; Edit4: TEdit; ComboBox2: TComboBox; lblResultat: TLabel; ComboBox3: TComboBox; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Déclarations privées } FMachineA, FMachineB : TBZMachine; FResultatMachine1, FResultatMachine2 : Integer; protected { Déclarations protégées } procedure ExecuteObserver(Observable: IBZObservable); public { Déclarations publiques } procedure AttachToObservable(Observable: IBZObservable); end;
Et là paf je me tape une exception le compteur de référence de l'interface IBZObservable me semble être le fautif. est-ce dû la propriété Observable3 ??? Bref c'est certain que ma construction pour l'objet "TBZMachine" est incorrecte mais je n'arrive pas à comprendre pourquoi et comment solutionner le problème. Ou bien c'est à cause de mes "fabriques" ????
Je vous ai mis un petit zip avec toutes les sources. Design Pattern.zip cela sera plus simple
Si des âme charitables passent par là. Et pourrais me corriger en me donnant des explications pour que je puisse comprendre, ça serait cool. Je vous remercie d'avance pour votre aide.
A+










Répondre avec citation
Partager