Pattern Observer

1. Problème initial

On trouve des classes possédant des attributs dont les valeurs changent régulièrement. De plus, un certain nombre de classes doit être tenu informé de l’évolution de ces valeurs. Il n’est pas rare d’être confronté à ce problème notamment en développant une classe métier et les classes d’affichages correspondantes.

Afin d’illustrer ce problème récurent, prenons un exemple volontairement simpliste. On considère une classe HeurePerso possédant dans le même attribut l’heure et la minute courante. Cette classe, et plus particulièrement son attribut, est utilisé pour l’affichage de l’heure courante dans une fenêtre. Pour cela, on définit une classe AfficheHeure qui se charge d’afficher l’heure et la minute courante dans une partie de la fenêtre. On peut alors se demander quelle démarche adopter pour que la classe chargée de l’affichage soit tenue informée en temps réel de l’heure courante stockée dans la classe HeurePerso ?

2. Solution associée

Sur l’exemple précédent on peut identifier deux solutions. Soit la classe d’affichage se charge de demander à la classe HeurePerso la valeur de son attribut soit c’est la classe HeurePerso qui informe la classe AfficheHeure lors de changements.

Il est facile de s’apercevoir que la première solution n’est pas la meilleure. En effet, quand la classe AfficheHeure devra t-elle questionner HeurePerso pour obtenir l’heure courante ? Toutes les minutes ? Toutes les secondes ?
Quelque soit l’intervalle choisi, soit l’heure ne sera pas précise soit on surchargera d’appels inutiles la classe HeurePerso.

La solution consiste donc à laisser la charge à la classe HeurePerso d’informer sa classe d’affichage de ses changements de valeurs. Cependant la classe HeurePerso doit pouvoir informer plusieurs classes d’affichage et cela en évitant de lier fortement les classes entre elles. C’est à dire qu’une modification des classes d’affichage ne doit pas engendrer de modification dans la classe métier et vice versa.

Comment peut-on faire ?

3. Définition

Le pattern Observateur (en anglais Observer) définit une relation entre objets de type un-à-plusieurs, de façon que, si un objet change d’état, tous ceux qui en dépendent en soient informés et mis à jour automatiquement.

Comment ça marche ?

Pour commencer il nous faut :
  • La classe qui est susceptible de changer : l’observable, celui qu’on va observer
  • La/les classes qui souhaite(nt) être notifiée(s) du/des changement(s) de la classe observable : ce sont les observers
On part donc avec les deux classes suivantes :



A noter que l’attribut etat est l’attribut qui risque d’être modifié dans le futur et c’est lors de ce changement que les Observateurs (observers) seront prévenus.

Mise à part cela le rôle principal de la classe ObservableConcret est d’avertir ses Observateurs qu’elle a modifié son état.
Par conséquent il faut qu’elle possède une liste de ses observateurs ainsi qu’une méthode pour ajouter des observateurs et aussi en supprimer. Ces méthodes seront appelées par les instances de ObserverConcret pour qu’elles puissent s’ajouter en tant qu’oberver pour une instance de ObservableConcret.

On obtient donc :



Ainsi à ajout d’un observateur via la méthode ajouterObservateur on l’ajoute automatiquement à la liste des observateurs.

Pourquoi ne pas faire l’inverse c’est-à-dire que ce soit les observateurs qui aient une liste des instances de ObservableConcret ? Tout simplement parce qu’il faut réfléchir à comment un observateur va alors faire pour savoir quand il y aura un changement. Il va regarder la valeur de etat toutes les secondes, minutes, heures ? Non et quand bien même il le ferait cela ne serait pas du tout optimisé et violerai bon nombre de patterns et de principes vus jusque-là.

Maintenant il nous faut ajouter la méthode qui va notifier tous ces observateurs. Cela donne donc :



Maintenant que notre ObservableConcret est prêt on peut passer à l’observateur. Quel est le rôle de ObserverConcret dans tout ça ? Son seul et unique rôle dans ce pattern est qu’il doit se mettre à jour dès qu’il a notifié d’un changement pour une instance d’ObservableConcret à laquelle il s’est abonné.

On ajoute donc la méthode actualiser :



Remarque : La méthode actualiser() prend en paramètre un ObservableConcret car l’ObserverConcret ne fera pas les mêmes actions en fonction de l’ObservableConcret qui a été modifié et qui l’a notifié.

Voici le principe du pattern. Néanmoins on peut encore l’améliorer.
En effet si l’on souhaite ajouter d’autre classe qu’on souhaite rendre observable ou observateur on va être obligé de redéfinir toutes ces méthodes ce qui est normal. On peut néanmoins s’assurer que chaque nouvel observable ou observateur possèdera les mêmes méthodes que ses confrères en définissant toutes ces méthodes dans des interfaces.

Cela donne donc :



4. Exemple

4.1. Description du problème

Afin d’illustrer l’implémentation du pattern Observateur en Java réalisons une petite application permettant de se positionner grâce au GPS. Le principe du Global Positioning System est simple. Une personne souhaitant connaître sa position utilise un récepteur GPS. Ce récepteur reçoit des informations (position, date précise…) d’au moins quatre satellites (sur un total de 24 satellites). Grâce à la date transmise, le récepteur peut calculer la distance le séparant du satellite dont il connaît la position. Il renouvelle l’opération avec trois autres satellites et peut donc en déduire sa position dans l’espace (procédé appelé là trilatération).

A chaque nouvelle mesure de notre GPS il modifie sa position ainsi que sa précision avec la méthode setMesure.

On souhaite donc qu’à chaque nouvelle modification il se produise deux affichages : l’affichage de la nouvelle position et de la nouvelle précision. Pour cela utiliser le pattern Observer/Observable que l’on vient de voir.

4.2. Question

1. Concevoir un diagramme de classes à l’image de celui vu dans la partie 7.1.3.
2. Ecrire le code de la classe GPS et des deux interfaces Observable et Observer
3. Ecrire le code des autres classes restantes (2)


4.3. Correction

1.

Considérons que notre ordinateur est relié à un récepteur GPS par un réseau sans fil. On va concevoir une classe nommée Gps qui va stocker les informations du récepteur (position et précision). Puis deux autres classes (AffichePosition et AffichePrecision) permettant d’afficher de deux façons différentes ces informations. Comme dans la définition du pattern Observateur, on trouve également deux interfaces Observateur et Observable.

Pour résumer la classe GPS sera observable et les classes AffichePosition et AffichePrecision seront ses observateurs. Voyons plus en détails le diagramme de classes puis l’implémentation de cette application.



Ce qu’il ne faut surtout pas oublier :
- Les classes AffichePosition et AffichePrecision implémentent Observer
- La classe GPS implémente Observable
- La classe GPS a des accesseurs pour ses attributs qui sont susceptibles d’être modifiés
- La classe GPS possède une liste de ses observateurs


2. 3.

On commence par écrire le code des deux interfaces :



On réalise ensuite la classe GPS :



On écrit le code des deux dernières classes restantes c’est-à-dire les observateurs.