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):

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

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;
Les valeurs de cet objet observable sont misent à jour au travers les événements OnChange des TEdit
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

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;
Jusque la tout fonctionne. Mais j'ai un doute sur l'utilisation du AS

Deuxième exemple au lieu d'employé un objet observateur, je donne la possibilité à ma fiche d'être cet observateur

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;
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
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;
Là également tout va bien.

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

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;
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
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;
En plus j'ai voulu rajouter des "Fabrique"
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+