WinDev et le Design pattern observateur

Design pattern de type comportement : observateur

v.formet

Ce tutoriel explique la mise en place du Design Pattern observateur dans l'EDI WinDev.

Commentez

- Titre : WinDev et le Design pattern observateur

- Auteur : v.formet

- Parution : 28 décembre 2015

- Public visé :

- Licence : Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright ® 2015 v. Formet. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Définition

- II. UML

- II-A. Héritage/Compositions

- II-B. Implémentation/Compositions

- II-C. Implémentation/Composition

- III. Mise en application

- III-A. Description du problème

- III-B. Observateur : version « POUSSER »

- III-B-1. Classe ac_adresseObservable

- III-B-2. ac_AdresseObservateur

- III-B-3. pc_AdresseClient

- III-B-4. pc_AdresseGoogleMaps

- III-B-5. pc_AdresseGrpChamp

- III-B-6. Test

- III-C. Observateur : version « TIRER »

- III-C-1. ac_AdresseObservateur

- III-C-2. pc_AdresseClient

- III-C-3. pc_AdresseGoogleMaps

- III-C-4. pc_AdresseGrpChamps

- III-C-5. Test

- III-D. Différence

- IV. Remerciements

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.

Fig. 1 : schéma UML

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

Fig. 2 : schéma UML

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

Fig. 3 : schéma UML

Source : http://blog.soat.fr/2013/04/le-design-pattern-observer-limplementation-proposee-par-microsoft-en-net-4-0/

Ce diagramme UML utilise deux notions :

- implémentation : les classes ConcreteSubject et ConcreteObserver implémentent les interfaces Subject et ObserverAuteur inconnu2015-11-03T11:49:13.35Police (dans les autres cas vous avez mis en texte non proportionnel). ;

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

Fig. 4 : 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

Code de la classe ac_AdresseObservable

1. Déclaration de ac_clientObservable

2. ac_AdresseObservable est une Classe, abstraite

4. FIN

6. PROCEDURE Constructeur()

9. PROCEDURE Destructeur()

12. // Résumé : Ajoute un observateur

13. // Syntaxe :

14. //EnregistreObservateur (<po_clientObservateur> est ac_AdresseObservateur dynamique)

15. //

16. // Paramètres :

17. //    po_clientObservateur (ac_AdresseObservateur dynamique) : objet de type observateur à ajouter à la liste

18. // Valeur de retour :

19. //    Aucune

20. //

21. // Exemple :

22. // lo_adrresseObservable:EnregistreObservateur(lo_clientObservateur)

23. //

24. PROCEDURE EnregistreObservateur(po_clientObservateur est un ac_AdresseObservateur dynamique)

27. // Résumé : Supprime un observateur à la liste

28. // Syntaxe :

29. //SupprimeObservateur (<po_clientObservateur> est ac_AdresseObservateur dynamique)

30. //

31. // Paramètres :

32. //    po_clientObservateur (ac_AdresseObservateur dynamique) : objet de type observateur à supprimer à la liste

33. // Valeur de retour :

34. //    Aucune

35. //

36. // Exemple :

37. // Indiquez ici un exemple d'utilisation.

38. // lo_adrresseObservable:SupprimeObservateur(lo_clientObservateur)

39. PROCEDURE SupprimeObservateur(po_clientObservateur est un ac_AdresseObservateur dynamique)

42. // Résumé : Notifie aux observateurs du changement d'état

43. // Syntaxe :

44. // notifierObservateurs ()

45. //

46. // Paramètres :

47. //    Aucun

48. // Valeur de retour :

49. //    Aucune

50. //

51. // Exemple :

52. // lo_adrresseObservable:notifierObservateurs()

53. //

54. PROCEDURE notifierObservateurs()

III-B-2 - ac_AdresseObservateur

Code la classe ac_AdresseObservateur

1. ac_AdresseObservateur est une Classe

3. FIN

5. PROCEDURE Constructeur()

8. PROCEDURE Destructeur()

11. // Résumé : Actualiser l'observateur avec les informations de l'observable

12. // Syntaxe :

13. //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)

14. //

15. // Paramètres :

16. //    ps_adresse1 (chaîne ANSI) : zone 1 adresse

17. //    ps_adresse2 (chaîne ANSI) : zone 2 adresse

18. //    ps_adresse3 (chaîne ANSI) : zone 3 adresse

19. //    ps_codePostal (chaîne ANSI) : code postal

20. //    ps_ville (chaîne ANSI) : ville

21. // Valeur de retour :

22. //    Aucune

23. //

24. // Exemple :

25. // :lst_observateurs[1]:Exe_Actualiser("1 rue du stade","","","57000","Metz").

26. //

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

Code de la classe pc_AdresseClient

1. pc_AdresseClient est une Classe

2. hérite de ac_AdresseObservable

4. PRIVÉE

5. mlst_Observateur est un tableau de 0 ac_AdresseObservateur dynamique

6. ms_adresse1 est une chaîne

7. ms_adresse2 est une chaîne

8. ms_adresse3 est une chaîne

9. ms_codePostal est une chaîne

10. ms_ville est une chaîne

11. FIN

13. PROCEDURE PUBLIQUE adresse1() : chaîne

15. RENVOYER ms_adresse1

16. PROCEDURE PUBLIQUE adresse1(Valeur est une chaîne)

18. ms_adresse1=Valeur

19. PROCEDURE PUBLIQUE adresse2() : chaîne

21. RENVOYER ms_adresse2

22. PROCEDURE PUBLIQUE adresse2(Valeur est une chaîne)

24. ms_adresse2=Valeur

25. PROCEDURE PUBLIQUE adresse3() : chaîne

27. RENVOYER ms_adresse3

28. PROCEDURE PUBLIQUE adresse3(Valeur est une chaîne)

30. ms_adresse3=Valeur

31. PROCEDURE PUBLIQUE codePostal() : chaîne

33. RENVOYER ms_codePostal

34. PROCEDURE PUBLIQUE codePostal(Valeur est une chaîne)

36. ms_codePostal=Valeur

37. PROCEDURE PUBLIQUE ville() : chaîne

39. RENVOYER ms_ville

40. PROCEDURE PUBLIQUE ville(Valeur est une chaîne)

42. ms_ville=Valeur

43. PROCEDURE Constructeur()

45. :mlst_Observateur=allouer un tableau de 0 ac_AdresseObservateur dynamique

46. PROCEDURE Destructeur()

50. PROCEDURE EnregistreObservateur(po_clientObservateur est un ac_AdresseObservateur dynamique)

52. Ajoute(:mlst_Observateur,po_clientObservateur)

53. PROCEDURE SupprimeObservateur(po_clientObservateur est un ac_AdresseObservateur dynamique)

55. POUR li_i=1 _A_ :mlst_Observateur..Occurrence

56. SI :mlst_Observateur[li_i] = po_clientObservateur ALORS

57. TableauSupprime(:mlst_Observateur,li_i)

58. SORTIR

59. FIN

60. FIN

63. // Redéfinition de la méthode ac_ClientObservable.notifierObservateurs

64. PROCEDURE notifierObservateurs()

66. lo_ObservateurTmp est un ac_AdresseObservateur dynamique

68. POUR li_i=1 _A_ :mlst_Observateur..Occurrence

69. lo_ObservateurTmp=:mlst_Observateur[li_i]

70. lo_ObservateurTmp:Exe_Actualiser(:ms_adresse1,:ms_adresse2,:ms_adresse3,:ms_codePostal,:ms_ville)

72. FIN

73. 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)

75. :ms_adresse1=ps_adresse1

76. :ms_adresse2=ps_adresse2

77. :ms_adresse3=ps_adresse3

78. :ms_codePostal=ps_codePostal

79. :ms_ville=ps_ville

81. :notifierObservateurs()

III-B-4 - pc_AdresseGoogleMaps

Code de la classe pc_AdresseGoogleMaps

1. pc_AdresseGoogleMaps est une Classe

2. hérite de ac_AdresseObservateur

3. FIN

5. PROCEDURE Constructeur(po_observable est un ac_AdresseObservable dynamique)

7. po_observable:EnregistreObservateur(objet)

9. PROCEDURE Destructeur()

12. // Redéfinition de la méthode ac_ClientObservateur.Exe_Actualiser

13. 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)

15. lo_XML est un xmlDocument

16. ls_latitude, ls_longitude est une chaîne

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

19. lo_XML=XMLOuvre(HTTPDonneRésultat(httpRésultat),depuisChaîne)

20. ls_latitude=lo_XML.GeocodeResponse.RESULT.geometry.location.lat

21. ls_longitude=lo_XML.GeocodeResponse.RESULT.geometry.location.lng

22. FIN

24. gsCodeHtmlPartie1  est une chaîne

25. gsCodeHtmlPartie1 =[

26. <!DOCTYPE html>

27. <html>

28. <head>

29. <title>Asynchronous Loading</title>

30. <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

31. <meta charset="utf-8">

32. <style>

33. html, body, #map-canvas {

34. height: 100%;

35. margin: 0px;

36. padding: 0px

37. }

38. </style>

39. <script>

41. var geocoder;

42. var map;

44. function initialize() {

45. var myLatlng = new google.maps.LatLng(%1,%2);

46. var mapOptions = {

47. zoom: 14,

48. center: myLatlng

49. }

50. var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

52. var marker = new google.maps.Marker({

53. position: myLatlng,

54. map: map,

55. title: 'Hello World!'

56. });

57. }

60. function loadScript() {

61. var script = document.createElement('script');

62. script.type = 'text/javascript';

63. script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp' +

64. '&signed_in=true&callback=initialize';

65. document.body.appendChild(script);

66. }

68. window.onload = loadScript;

70. </script>

72. </head>

73. <body>

75. <div id="map-canvas"></div>

77. </body>

78. </html>

79. ]

82. {MaFenêtre..Nom+".HTML1",indChamp} = ChaîneConstruit(gsCodeHtmlPartie1,ls_latitude,ls_longitude)

III-B-5 - pc_AdresseGrpChamp

Code de la classe pc_AdresseGrpoChamp

1. pc_AdresseGRpChamp est une Classe

2. hérite de ac_AdresseObservateur

3. FIN

5. PROCEDURE Constructeur(po_Observable est ac_AdresseObservable dynamique)

7. po_Observable:EnregistreObservateur(objet)

9. PROCEDURE Destructeur()

12. // Redéfinition de la méthode ac_ClientObservateur.Exe_Actualiser

13. 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)

15. {MaFenêtre..Nom+".sai_Adresse1",indChamp}=ps_adresse1

16. {MaFenêtre..Nom+".sai_Adresse2",indChamp}=ps_adresse2

17. {MaFenêtre..Nom+".sai_Adresse3",indChamp}=ps_adresse3

18. {MaFenêtre..Nom+".sai_CodePostal",indChamp}=ps_codePostal

19. {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 :

Code du test

1. go_adresseClient est pc_AdresseClient()

2. go_GrpClient est un pc_AdresseGRpChamp(go_adresseClient)

3. go_GoogleMapsClient est pc_AdresseGoogleMaps(go_adresseClient)

Code la partie « sélection d'une ligne » de la table :

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

Code d'initialisation de la table

1. TableAjouteLigne(Tab_LstAdresse,"1 rue du stade","","","57000","Metz")

2. TableAjouteLigne(Tab_LstAdresse,"1 rue du l'eglise","","","57000","Metz")

3. TableAjouteLigne(Tab_LstAdresse,"1 rue du serpenoise","","","57000","Metz")

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

Nouvelle définition de la fonction Exe_actualiser

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

Fonction notifierObservateurs

1. PROCEDURE notifierObservateurs()

3. lo_ObservateurTmp est un ac_AdresseObservateur dynamique

5. POUR li_i=1 _A_ :mlst_Observateur..Occurrence

6. lo_ObservateurTmp=:mlst_Observateur[li_i]

7. lo_ObservateurTmp:Exe_Actualiser(objet)

8. FIN

III-C-3 - pc_AdresseGoogleMaps

Modification de la fonction Exe_Actualiser.

Code de la fonction Exe_Actualiser

1. PROCEDURE VIRTUELLE Exe_Actualiser(po_adresseObservable est un ac_AdresseObservable dynamique)

3. //----->Définition des variables

4. ls_latitude, ls_longitude est une chaîne

5. lo_observateurTemps est un pc_AdresseClient dynamique

6. lo_XML est un xmlDocument

9. SELON po_adresseObservable..Classe

11. CAS "pc_AdresseClient"

12. lo_observateurTemps<-po_adresseObservable

14. AUTRE CAS

16. FIN

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

19. lo_XML=XMLOuvre(HTTPDonneRésultat(httpRésultat),depuisChaîne)

20. ls_latitude=lo_XML.GeocodeResponse.result.geometry.location.lat

21. ls_longitude=lo_XML.GeocodeResponse.result.geometry.location.lng

22. FIN

25. gsCodeHtmlPartie1  est une chaîne

28. gsCodeHtmlPartie1 =[

29. <!DOCTYPE html>

30. <html>

31. <head>

32. <title>Asynchronous Loading</title>

33. <meta name="viewport" content="initial-scale=1.0, user-scalable=no">

34. <meta charset="utf-8">

35. <style>

36. html, body, #map-canvas {

37. height: 100%;

38. margin: 0px;

39. padding: 0px

40. }

41. </style>

42. <script>

44. var geocoder;

45. var map;

47. function initialize() {

48. var myLatlng = new google.maps.LatLng(%1,%2);

49. var mapOptions = {

50. zoom: 14,

51. center: myLatlng

52. }

53. var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);

55. var marker = new google.maps.Marker({

56. position: myLatlng,

57. map: map,

58. title: 'Hello World!'

59. });

60. }

63. function loadScript() {

64. var script = document.createElement('script');

65. script.type = 'text/javascript';

66. script.src = 'https://maps.googleapis.com/maps/api/js?v=3.exp' +

67. '&signed_in=true&callback=initialize';

68. document.body.appendChild(script);

69. }

71. window.onload = loadScript;

73. </script>

75. </head>

76. <body>

78. <div id="map-canvas"></div>

80. </body>

82. </html>

83. ]

85. {MaFenêtre..Nom+".HTML1",indChamp} = ChaîneConstruit(gsCodeHtmlPartie1,ls_latitude,ls_longitude)

III-C-4 - pc_AdresseGrpChamps

Modification de la fonction Exe_Actualiser.

Fonction Exe_Actualiser

1. PROCEDURE VIRTUELLE Exe_Actualiser(po_adresseObservable est un ac_AdresseObservable dynamique)

3. lo_AdresseObservableTemp est un pc_AdresseClient dynamique

5. SELON po_adresseObservable..Classe

7. CAS "pc_AdresseClient"

8. lo_AdresseObservableTemp<-po_adresseObservable

10. AUTRE CAS

12. FIN

14. {MaFenêtre..Nom+".sai_Adresse1",indChamp}=lo_AdresseObservableTemp:adresse1

15. {MaFenêtre..Nom+".sai_Adresse2",indChamp}=lo_AdresseObservableTemp:adresse2

16. {MaFenêtre..Nom+".sai_Adresse3",indChamp}=lo_AdresseObservableTemp:adresse3

17. {MaFenêtre..Nom+".sai_CodePostal",indChamp}=lo_AdresseObservableTemp:codePostal

18. {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.6. 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 :

Entête de la fonction Exe_actualiser

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

Nouveau code de la fonction notifierObservateur

1. PROCEDURE notifierObservateurs()

3. lo_ObservateurTmp est un ac_AdresseObservateur dynamique

5. POUR li_i=1 _A_ :mlst_Observateur..Occurrence

6. lo_ObservateurTmp=:mlst_Observateur[li_i]

7. lo_ObservateurTmp:Exe_Actualiser(:ms_adresse1,:ms_adresse2,:ms_adresse3,:ms_codePostal,:ms_ville,:ms_pays)

9. 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 : LittleWhite ;

- relecture orthographique : ClaudeLELOUP ;