I. Définition▲
Avant de nous lancer tête baissée dans la programmation, nous allons découvrir plusieurs définitions référencées lors de la mise en place de cet article :
- Livre : Design patterns - Tête la première
Le pattern observateur définit une relation de type un-à-plusieurs, de façon que, lorsqu'un objet change d'état, tous ceux qui en dépendent en soient notifiés et soient mis à jour automatiquement.
- Livre Design patterns en Java
Le Pattern Observer a pour objectif de construire une dépendance entre un sujet et des observateurs de sorte que chaque modification du sujet soit notifiée aux observateurs afin qu'ils puissent mettre à jour leur état.
- Wikipédia
Le patron de conception observateur/observable est utilisé en programmation pour envoyer un signal à des modules qui jouent le rôle d'observateur. En cas de notification, les observateurs effectuent alors l'action adéquate en fonction des informations qui parviennent depuis les modules qu'ils observent (les « observables »).
Comme nous venons de voir avec ces définitions, nous avons deux entités :
- sujet ou observable ;
- observateurs.
Le sujet envoie une notification à ses observateurs, ceux-ci se mettent à jour dès réception de celle-ci. Vous remarquerez que dans mon explication j'ai utilisé le mot « ses », sous-entendant l'utilisation de la composition. Ce Design pattern peut être utilisé dans les cas suivants :
• la modification d'un objet entraîne la modification d'autres objets déterminés dynamiquement ;
• un objet veut prévenir d'autres objets sans connaître leur type. On parlera de couplage faible.
II. UML▲
Après avoir acquis un ensemble de définitions, nous allons nous intéresser à la conception de ce Design pattern. Au même titre que les définitions varient dans la manière d'exprimer l'idée, les diagrammes UML diffèrent. Même si au niveau des définitions, l'idée reste la même, les différences de diagramme UML entraînent des choix de programmation différents. Dans ce paragraphe nous allons voir trois diagrammes différents et mon avis sur ceux-ci.
II-A. Héritage/Compositions ▲
Source : http://www.cs.mcgill.ca/~hv/classes/CS400/01.hchen/doc/observer/observer.html
Ce diagramme UML utilise deux notions :
- héritage : les classes ConcreteSubject et ConcreteObserver héritent des classes Subject et Observer;
- composition : la classe Subject possède un membre regroupant toutes les références des Observer. La classe ConcreteObserve possède un membre référençant le sujet.
Pourquoi ne pas utiliser cette conception ?
- L'héritage : utiliser l'héritage lie fortement nos objets alors que dans le premier paragraphe, nous avons parlé de couplage faible.
- La double composition : si l'observateur possède la référence de ces observables, pourquoi l'observateur a-t-il une référence de l'observable ? Nous pourrions remplacer cette référence par un passage de paramètre.
II-B. Implémentation/Compositions▲
Source : http://design-patterns.fr/observateur
Ce diagramme UML utilise deux notions :
- implémentation : les classes ConcreteSubject et ConcreteObserver implémentent les interfaces Subject et Observer ;
- composition : la classe Subject possède un membre regroupant toutes les références des Observer. La classe ConcreteObserve possède un membre référençant le sujet
Pourquoi ne pas utiliser cette description ?
La double composition : si l'observateur possède la référence de ces observables, pourquoi l'observateur a-t-il une référence de l'observable. Nous pourrions remplacer cette référence par un passage de paramètre.
II-C. Implémentation/Composition▲
Ce diagramme UML utilise deux notions :
- implémentation : les classes ConcreteSubject et ConcreteObserver implémentent les interfaces Subject et Observer
- composition : la classe Subject possède un membre regroupant toutes les références des Observer.
Cette conception rectifie les défauts des deux exemples précédents. C'est celle-ci que nous allons utiliser dans la suite de la publication. Ci-dessous les rôles des différents objets du diagramme :
- Subject : est l'interface de l'objet à observer. Elle fournit les méthodes (ajouter, modifier, supprimer, notifier) à redéfinir dans les classes implémentant cette interface ;
- ConcreteSubject : est l'implémentation de l'interface à observer. Lorsqu'une valeur est modifiée, la méthode notifyObserver()est appelée ;
- Observer: est l'interface de l'observateur. Il déclare la/les méthode(s) de l'interface Observer ;
- ConcreteObserverOne et ConcreteObserverTwo : implémentent l'interface Observer. La partie cliente indique à l'objet Subject les objets Observer qu'il (objet Subject) avertira.
Les observateurs peuvent récupérer le ou les états de l'observable via des fonctions, accesseurs de celui-ci ; cette solution est nommée « TIRER ». Effectivement les observateurs vont tirer les informations de l'observable. Mais une seconde variante existe, celle-ci est nommée « POUSSER ». À l'inverse de la première, c'est l'observable qui va fournir les informations aux observateurs lors de la modification de l'état. Nous allons maintenant voir à travers un cas concret la mise en place de ce Design Pattern avec ses deux variantes.
III. Mise en application▲
III-A. Description du problème▲
Nous devons développer une fenêtre avec les descriptions suivantes :
• une table avec les adresses des clients ;
• un ensemble de champs (numéro, type de voie, nom de voie, code postal, ville) pour visualiser cette adresse ;
• un champ carte de type Google Maps.
Exemple d'interface graphique :
Pour développer ce problème, nous allons lister les classes en partant de schéma UML :
- Subject: représente l'interface adresse observable : classe abstraite ac_adresseObservable.
|
WinDev en version 20 et antérieures ne permettent pas de déclarer des interfaces. Pour simuler la notion d'interface, nous allons utiliser des classes abstraites avec des méthodes vides. |
- Observer : représente les interfaces des adresses Observer : classe abstraite ac_adresseObservateur.
- ConcreteSubject: représente l'entité observable : classe pc_AdresseClient.
- ConcreteObserverOne: représente l'entité observer groupe de champs « adresse » : classe pc_AdresseGrpChamp.
- ConcreteObserverTwo: représente l'entité observer champ Google Maps : classe pc_AdresseGoogleMaps.
Après avoir listé les sujets tâches à effectuer, nous allons mettre en application selon les deux versions du Design pattern.
III-B. Observateur : version « POUSSER »▲
Dans cette version, c'est l'observable qui met à disposition son/ses état(s) aux observateurs.
III-B-1. Classe ac_adresseObservable▲
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.
Déclaration de
ac_clientObservable
ac_AdresseObservable est
une
Classe
,
abstraite
FIN
PROCEDURE
Constructeur()
PROCEDURE
Destructeur
()
// Résumé : Ajoute un observateur
// Syntaxe :
//EnregistreObservateur (<po_clientObservateur> est ac_AdresseObservateur dynamique)
//
// Paramètres :
// po_clientObservateur (ac_AdresseObservateur dynamique) : objet de type observateur à ajouter à la liste
// Valeur de retour :
// Aucune
//
// Exemple :
// lo_adrresseObservable:EnregistreObservateur(lo_clientObservateur)
//
PROCEDURE
EnregistreObservateur(
po_clientObservateur est
un
ac_AdresseObservateur dynamique
)
// Résumé : Supprime un observateur à la liste
// Syntaxe :
//SupprimeObservateur (<po_clientObservateur> est ac_AdresseObservateur dynamique)
//
// Paramètres :
// po_clientObservateur (ac_AdresseObservateur dynamique) : objet de type observateur à supprimer à la liste
// Valeur de retour :
// Aucune
//
// Exemple :
// Indiquez ici un exemple d'utilisation.
// lo_adrresseObservable:SupprimeObservateur(lo_clientObservateur)
PROCEDURE
SupprimeObservateur(
po_clientObservateur est
un
ac_AdresseObservateur dynamique
)
// Résumé : Notifie aux observateurs du changement d'état
// Syntaxe :
// notifierObservateurs ()
//
// Paramètres :
// Aucun
// Valeur de retour :
// Aucune
//
// Exemple :
// lo_adrresseObservable:notifierObservateurs()
//
PROCEDURE
notifierObservateurs()
III-B-2. ac_AdresseObservateur▲
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.
ac_AdresseObservateur est
une
Classe
FIN
PROCEDURE
Constructeur()
PROCEDURE
Destructeur
()
// Résumé : Actualiser l'observateur avec les informations de l'observable
// Syntaxe :
//Exe_Actualiser (<ps_adresse1> est chaîne, <ps_adresse2> est chaîne, <ps_adresse3> est chaîne, <ps_codePostal> est chaîne, <ps_ville> est chaîne)
//
// Paramètres :
// ps_adresse1 (chaîne ANSI) : zone 1 adresse
// ps_adresse2 (chaîne ANSI) : zone 2 adresse
// ps_adresse3 (chaîne ANSI) : zone 3 adresse
// ps_codePostal (chaîne ANSI) : code postal
// ps_ville (chaîne ANSI) : ville
// Valeur de retour :
// Aucune
//
// Exemple :
// :lst_observateurs[1]:Exe_Actualiser("1 rue du stade","","","57000","Metz").
//
PROCEDURE
Exe_Actualiser(
ps_adresse1 est
une
chaîne
,
ps_adresse2 est
une
chaîne
,
ps_adresse3 est
une
chaîne
,
ps_codePostal est
une
chaîne
,
ps_ville est
une
chaîne
)
III-B-3. pc_AdresseClient▲
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.
75.
76.
77.
78.
79.
80.
81.
pc_AdresseClient est
une
Classe
hérite
de
ac_AdresseObservable
PRIVÉE
mlst_Observateur est
un
tableau
de
0
ac_AdresseObservateur dynamique
ms_adresse1 est
une
chaîne
ms_adresse2 est
une
chaîne
ms_adresse3 est
une
chaîne
ms_codePostal est
une
chaîne
ms_ville est
une
chaîne
FIN
PROCEDURE
PUBLIQUE adresse1()
:
chaîne
RENVOYER
ms_adresse1
PROCEDURE
PUBLIQUE adresse1(
Valeur
est
une
chaîne
)
ms_adresse1=
Valeur
PROCEDURE
PUBLIQUE adresse2()
:
chaîne
RENVOYER
ms_adresse2
PROCEDURE
PUBLIQUE adresse2(
Valeur
est
une
chaîne
)
ms_adresse2=
Valeur
PROCEDURE
PUBLIQUE adresse3()
:
chaîne
RENVOYER
ms_adresse3
PROCEDURE
PUBLIQUE adresse3(
Valeur
est
une
chaîne
)
ms_adresse3=
Valeur
PROCEDURE
PUBLIQUE codePostal
()
:
chaîne
RENVOYER
ms_codePostal
PROCEDURE
PUBLIQUE codePostal
(
Valeur
est
une
chaîne
)
ms_codePostal=
Valeur
PROCEDURE
PUBLIQUE ville
()
:
chaîne
RENVOYER
ms_ville
PROCEDURE
PUBLIQUE ville
(
Valeur
est
une
chaîne
)
ms_ville=
Valeur
PROCEDURE
Constructeur()
:
mlst_Observateur=
allouer
un
tableau
de
0
ac_AdresseObservateur dynamique
PROCEDURE
Destructeur
()
PROCEDURE
EnregistreObservateur(
po_clientObservateur est
un
ac_AdresseObservateur dynamique
)
Ajoute(:
mlst_Observateur,
po_clientObservateur)
PROCEDURE
SupprimeObservateur(
po_clientObservateur est
un
ac_AdresseObservateur dynamique
)
POUR
li_i=
1
_A_ :
mlst_Observateur..
Occurrence
SI
:
mlst_Observateur[
li_i]
=
po_clientObservateur ALORS
TableauSupprime
(:
mlst_Observateur,
li_i)
SORTIR
FIN
FIN
// Redéfinition de la méthode ac_ClientObservable.notifierObservateurs
PROCEDURE
notifierObservateurs()
lo_ObservateurTmp est
un
ac_AdresseObservateur dynamique
POUR
li_i=
1
_A_ :
mlst_Observateur..
Occurrence
lo_ObservateurTmp=:
mlst_Observateur[
li_i]
lo_ObservateurTmp:
Exe_Actualiser(:
ms_adresse1,:
ms_adresse2,:
ms_adresse3,:
ms_codePostal,:
ms_ville)
FIN
PROCEDURE
Maj_Adresse(
ps_adresse1 est
une
chaîne
,
ps_adresse2 est
une
chaîne
,
ps_adresse3 est
une
chaîne
,
ps_codePostal est
une
chaîne
,
ps_ville est
une
chaîne
)
:
ms_adresse1=
ps_adresse1
:
ms_adresse2=
ps_adresse2
:
ms_adresse3=
ps_adresse3
:
ms_codePostal=
ps_codePostal
:
ms_ville=
ps_ville
:
notifierObservateurs()
III-B-4. pc_AdresseGoogleMaps▲
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.
75.
76.
77.
78.
79.
80.
81.
82.
pc_AdresseGoogleMaps est
une
Classe
hérite
de
ac_AdresseObservateur
FIN
PROCEDURE
Constructeur(
po_observable est
un
ac_AdresseObservable dynamique
)
po_observable:
EnregistreObservateur(
objet
)
PROCEDURE
Destructeur
()
// Redéfinition de la méthode ac_ClientObservateur.Exe_Actualiser
PROCEDURE
Exe_Actualiser(
ps_adresse1 est
une
chaîne
,
ps_adresse2 est
une
chaîne
,
ps_adresse3 est
une
chaîne
,
ps_codePostal est
une
chaîne
,
ps_ville est
une
chaîne
)
lo_XML est
un
xmlDocument
ls_latitude,
ls_longitude est
une
chaîne
SI
HTTPRequête
(
"https://maps.googleapis.com/maps/api/geocode/xml?address="
+
ps_adresse1+
"+"
+
ps_adresse2+
"+"
+
ps_adresse3+
",+"
+
ps_codePostal+
"+"
+
ps_ville+
"&key=AIzaSyD5DNLIFxKdrF-PJngyDx7LDIoXNwwhsto"
)
ALORS
lo_XML=
XMLOuvre(
HTTPDonneRésultat
(
httpRésultat),
depuisChaîne)
ls_latitude=
lo_XML.
GeocodeResponse.
RESULT
.
geometry.
location.
lat
ls_longitude=
lo_XML.
GeocodeResponse.
RESULT
.
geometry.
location.
lng
FIN
gsCodeHtmlPartie1 est
une
chaîne
gsCodeHtmlPartie1 =
[
<!DOCTYPE html>
<html>
<head>
<title>Asynchronous Loading</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<style>
html, body, #map-canvas {
height: 100%;
margin: 0px;
padding: 0px
}
</style>
<script>
var geocoder;
var map;
function initialize() {
var myLatlng = new google.maps.LatLng(%1,%2);
var mapOptions = {
zoom: 14,
center: myLatlng
}
var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
var marker = new google.maps.Marker({
position: myLatlng,
map: map,
title: 'Hello World!'
});
}
function loadScript() {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp' +
'&signed_in=true&callback=initialize';
document.body.appendChild(script);
}
window.onload = loadScript;
</script>
</head>
<body>
<div id="map-canvas"></div>
</body>
</html>
]
{
MaFenêtre
..
Nom
+
".HTML1"
,
indChamp
}
=
ChaîneConstruit
(
gsCodeHtmlPartie1,
ls_latitude,
ls_longitude)
III-B-5. pc_AdresseGrpChamp▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
pc_AdresseGRpChamp est
une
Classe
hérite
de
ac_AdresseObservateur
FIN
PROCEDURE
Constructeur(
po_Observable est
ac_AdresseObservable dynamique
)
po_Observable:
EnregistreObservateur(
objet
)
PROCEDURE
Destructeur
()
// Redéfinition de la méthode ac_ClientObservateur.Exe_Actualiser
PROCEDURE
Exe_Actualiser(
ps_adresse1 est
une
chaîne
,
ps_adresse2 est
une
chaîne
,
ps_adresse3 est
une
chaîne
,
ps_codePostal est
une
chaîne
,
ps_ville est
une
chaîne
)
{
MaFenêtre
..
Nom
+
".sai_Adresse1"
,
indChamp
}=
ps_adresse1
{
MaFenêtre
..
Nom
+
".sai_Adresse2"
,
indChamp
}=
ps_adresse2
{
MaFenêtre
..
Nom
+
".sai_Adresse3"
,
indChamp
}=
ps_adresse3
{
MaFenêtre
..
Nom
+
".sai_CodePostal"
,
indChamp
}=
ps_codePostal
{
MaFenêtre
..
Nom
+
".sai_Ville"
,
indChamp
}=
ps_ville
III-B-6. Test▲
Ci-dessous le code pour tester la version « pousser ». Déclaration des variables dans la partie globale de la fenêtre :
2.
3.
go_adresseClient est
pc_AdresseClient()
go_GrpClient est
un
pc_AdresseGRpChamp(
go_adresseClient)
go_GoogleMapsClient est
pc_AdresseGoogleMaps(
go_adresseClient)
Code la partie « sélection d'une ligne » de la table :
go_adresseClient:
Maj_Adresse(
Tab_LstAdresse.
Col_Adresse1[
Tab_LstAdresse],
Tab_LstAdresse.
Col_Adresse2[
Tab_LstAdresse],
Tab_LstAdresse.
Col_Adresse3[
Tab_LstAdresse],
Tab_LstAdresse.
Col_CodePostal[
Tab_LstAdresse],
Tab_LstAdresse.
Col_Ville[
Tab_LstAdresse])
Ci-dessous le code d'initialisation de la table
2.
3.
4.
TableAjouteLigne(
Tab_LstAdresse,
"1 rue du stade"
,
""
,
""
,
"57000"
,
"Metz"
)
TableAjouteLigne(
Tab_LstAdresse,
"1 rue du l'eglise"
,
""
,
""
,
"57000"
,
"Metz"
)
TableAjouteLigne(
Tab_LstAdresse,
"1 rue du serpenoise"
,
""
,
""
,
"57000"
,
"Metz"
)
TableAjouteLigne(
Tab_LstAdresse,
"1 rue du dupon des loges"
,
""
,
""
,
"57000"
,
"Metz"
)
Lançons la fenêtre, et lors du clic sur une ligne du tableau nous constatons que les champs adresse et la carte se mettent à jour.
III-C. Observateur : version « TIRER »▲
Dans cette version, ce sont les observateurs qui récupèrent le ou les état(s) de l'observable. Dans ce paragraphe, nous n'allons pas revoir l'ensemble du code des classes, mais uniquement les parties qui varient par rapport à la version « POUSSER ».
III-C-1. ac_AdresseObservateur▲
Modification de la signature de la fonction Exe_Actualiser.
PROCEDURE
VIRTUELLE Exe_Actualiser(
po_observale est
un
ac_AdresseObservable dynamique
)
III-C-2. pc_AdresseClient▲
Modification de la fonction notifierObservateurs pour faire appel à la nouvelle fonction Exe_Actualiser.
2.
3.
4.
5.
6.
7.
8.
PROCEDURE
notifierObservateurs()
lo_ObservateurTmp est
un
ac_AdresseObservateur dynamique
POUR
li_i=
1
_A_ :
mlst_Observateur..
Occurrence
lo_ObservateurTmp=:
mlst_Observateur[
li_i]
lo_ObservateurTmp:
Exe_Actualiser(
objet
)
FIN
III-C-3. pc_AdresseGoogleMaps▲
Modification de la fonction Exe_Actualiser.
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.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
PROCEDURE
VIRTUELLE Exe_Actualiser(
po_adresseObservable est
un
ac_AdresseObservable dynamique
)
//----->Définition des variables
ls_latitude,
ls_longitude est
une
chaîne
lo_observateurTemps est
un
pc_AdresseClient dynamique
lo_XML est
un
xmlDocument
SELON
po_adresseObservable..
Classe
CAS
"pc_AdresseClient"
lo_observateurTemps<-
po_adresseObservable
AUTRE
CAS
FIN
SI
HTTPRequête
(
"https://maps.googleapis.com/maps/api/geocode/xml?address="
+
lo_observateurTemps:
adresse1+
"+"
+
lo_observateurTemps:
adresse2+
"+"
+
lo_observateurTemps:
adresse3+
",+"
+
lo_observateurTemps:
codePostal
+
"+"
+
lo_observateurTemps:
ville
+
"&key=AIzaSyD5DNLIFxKdrF-PJngyDx7LDIoXNwwhsto"
)
ALORS
lo_XML=
XMLOuvre(
HTTPDonneRésultat
(
httpRésultat),
depuisChaîne)
ls_latitude=
lo_XML.
GeocodeResponse.
result
.
geometry.
location.
lat
ls_longitude=
lo_XML.
GeocodeResponse.
result
.
geometry.
location.
lng
FIN
gsCodeHtmlPartie1 est
une
chaîne
gsCodeHtmlPartie1 =
[
<!DOCTYPE html>
<html>
<head>
<title>Asynchronous Loading</title>
<meta name="viewport" content="initial-scale=1.0, user-scalable=no">
<meta charset="utf-8">
<style>
html, body, #map-canvas {
height: 100%;
margin: 0px;
padding: 0px
}
</style>
<script>
var geocoder;
var map;
function initialize() {
var myLatlng = new google.maps.LatLng(%1,%2);
var mapOptions = {
zoom: 14,
center: myLatlng
}
var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
var marker = new google.maps.Marker({
position: myLatlng,
map: map,
title: 'Hello World!'
});
}
function loadScript() {
var script = document.createElement('script');
script.type = 'text/javascript';
script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp' +
'&signed_in=true&callback=initialize';
document.body.appendChild(script);
}
window.onload = loadScript;
</script>
</head>
<body>
<div id="map-canvas"></div>
</body>
</html>
]
{
MaFenêtre
..
Nom
+
".HTML1"
,
indChamp
}
=
ChaîneConstruit
(
gsCodeHtmlPartie1,
ls_latitude,
ls_longitude)
III-C-4. pc_AdresseGrpChamps▲
Modification de la fonction Exe_Actualiser.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
PROCEDURE
VIRTUELLE Exe_Actualiser(
po_adresseObservable est
un
ac_AdresseObservable dynamique
)
lo_AdresseObservableTemp est
un
pc_AdresseClient dynamique
SELON
po_adresseObservable..
Classe
CAS
"pc_AdresseClient"
lo_AdresseObservableTemp<-
po_adresseObservable
AUTRE
CAS
FIN
{
MaFenêtre
..
Nom
+
".sai_Adresse1"
,
indChamp
}=
lo_AdresseObservableTemp:
adresse1
{
MaFenêtre
..
Nom
+
".sai_Adresse2"
,
indChamp
}=
lo_AdresseObservableTemp:
adresse2
{
MaFenêtre
..
Nom
+
".sai_Adresse3"
,
indChamp
}=
lo_AdresseObservableTemp:
adresse3
{
MaFenêtre
..
Nom
+
".sai_CodePostal"
,
indChamp
}=
lo_AdresseObservableTemp:
codePostal
{
MaFenêtre
..
Nom
+
".sai_Ville"
,
indChamp
}=
lo_AdresseObservableTemp:
ville
III-C-5. Test▲
Pour les tests, nous utilisons le même jeu d'essai que celui du paragraphe III.B.6Test. Et nous constatons que les champs se mettent à jour lors de la sélection d'une adresse dans la table.
III-D. Différence▲
Nous venons de mettre en application les deux versions du Design pattern observateur. Notons les différences et simulons une évolution de cahier des charges. Comme évoqué ci-dessus, la différence entre ces deux versions est la façon dont les observateurs obtiennent la modification de l'état de l'observateur. Dans la version « POUSSER » la classe observable met à disposition ses états en paramètres alors que dans la version « TIRER » l'observateur va chercher les informations via un pointeur. Que se passe-t-il si nous modifions notre observable ? Dans notre cas rajoutons le membre pays.
Pour la version « POUSSER », nous devons modifier la fonction Exe_Actualiser de l'interface ac_AdresseObservable en ajoutant un paramètre. La signature de la fonction sera :
PROCEDURE
VIRTUELLE Exe_Actualiser(
ps_adresse1 est
une
chaîne
,
ps_adresse2 est
une
chaîne
,
ps_adresse3 est
une
chaîne
,
ps_codePostal est
une
chaîne
,
ps_ville est
une
chaîne
,
ps_pays est
une
chaîne
)
Et modifier l'appel à cette fonction dans la procédure notifierObservateur de la classe pc_AdresseClient.
2.
3.
4.
5.
6.
7.
8.
9.
PROCEDURE
notifierObservateurs()
lo_ObservateurTmp est
un
ac_AdresseObservateur dynamique
POUR
li_i=
1
_A_ :
mlst_Observateur..
Occurrence
lo_ObservateurTmp=:
mlst_Observateur[
li_i]
lo_ObservateurTmp:
Exe_Actualiser(:
ms_adresse1,:
ms_adresse2,:
ms_adresse3,:
ms_codePostal,:
ms_ville,:
ms_pays)
FIN
Imaginons que cette fonction soit utilisée dans différents endroits de notre programme : nous devons répercuter cette modification pour chaque appel. Nous avons une forte dépendance et de plus, nous savons que chaque modification entraîne des risques de régression. Pour la version « TIRER », l'observable reçoit en paramètre un pointeur, dans le cas de l'ajout du nouveau membre, nous n'effectuons pas de modification sur l'entête de la procédure Exe_Actualiser et donc aucun impact sur les appels. La seule modification qui est faite est dans le corps de la fonction de Exe_actualiser pour prendre en charge ce nouveau membre via sa propriété.
Dans cette dernière version, nous avons lié faiblement l'observable à ses observateurs. En effet, si l'observateur dispose d'un pointeur vers l'objet observable et que la classe observable évolue en ajoutant un autre état. L'observateur souhaitant se tenir informé de cet état aura juste à appeler l'accesseur correspondant.
IV. Remerciements▲
Merci à tous ceux qui m'ont aidé à la mise en œuvre de cette publication :
- relecture technique : LittleWhiteProfil Developpez de LittleWhite ;
- relecture orthographique : ClaudeLELOUP ;