Design pattern Observateur en Java : positionnement via un GPS

Rédigé par Mathieu G. - Ecrit le 08/05/2007 et mis à jour le 31/12/2022

Implémentation du pattern Observateur en Java et utilisation de l’API correspondante. L’exemple présenté est le positionnement par GPS.


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). Si vous désirez en savoir plus sur le GPS vous pouvez consulter l’article de Wikipédia sur ce sujet.

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 (positionnement, précision…). Puis deux autres classes (AfficheResume et AfficheComplet) 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 AfficheResume et AfficheComplet seront ses observateurs. Voyons plus en détails le diagramme UML et l’implémentation de cette application.


Implémentation d’une solution personnelle basée sur Observateur



Sur ce diagramme UML on reconnaît aisément le pattern Observateur. La classe Gps est l’observable et possède deux observateurs potentiels que sont AfficheResume et AfficheComplet. Commençons par regarder la définition des deux interfaces.

Définition des interfaces Observateur et Observable
// Interface implémentée par tous les observateurs.
public interface Observateur
{
        // Méthode appelée automatiquement lorsque l'état (position ou précision) du GPS change.
        public void actualiser(Observable o);
}
// Interface implémentée par toutes les classes souhaitant avoir des observateurs à leur écoute.
public interface Observable
{
        // Méthode permettant d'ajouter (abonner) un observateur.
        public void ajouterObservateur(Observateur o);
        // Méthode permettant de supprimer (résilier) un observateur.
        public void supprimerObservateur(Observateur o);
        // Méthode qui permet d'avertir tous les observateurs lors d'un changement d'état.
        public void notifierObservateurs();
}

L’interface Observateur impose aux observateurs d’implémenter la méthode actualiser(Observable). Celle-ci sera appelée automatiquement lors d’un changement d’état dans la classe observable. L’interface Observable oblige les classes souhaitant disposer d’observateurs d’implémenter trois méthodes. Les méthodes ajouterObservateur(Observateur) et supprimerObservateur(Observateur) permettent, respectivement, d’ajouter (abonner) et de supprimer (désabonner) un observateur. La méthode notifierObservateurs() se charge d’avertir tous les observateurs abonnés d’un changement d’état.

Implémentation de la classe Gps
// Classe représentant un GPS (appareil permettant de connaître sa position).
public class Gps implements Observable
{
        private String position;// Position du GPS.
        private int precision;// Précision accordé à cette position (suivant le nombre de satellites utilisés).
        private ArrayList tabObservateur;// Tableau d'observateurs.
        
        // Constructeur.
        public Gps()
        {
                position="inconnue";
                precision=0;
                tabObservateur=new ArrayList();
        }
        
        // Permet d'ajouter (abonner) un observateur à l'écoute du GPS.
        public void ajouterObservateur(Observateur o)
        {
                tabObservateur.add(o); 
        }
        
        // Permet de supprimer (résilier) un observateur écoutant le GPS
        public void supprimerObservateur(Observateur o)
        {
                tabObservateur.remove(o);              
        }
 
        // Méthode permettant de notifier tous les observateurs lors d'un changement d'état du GPS.
        public void notifierObservateurs()
        {
                for(int i=0;i<tabObservateur.size();i++)
                {
                        Observateur o = tabObservateur.get(i);
                        o.actualiser(this);// On utilise la méthode "tiré".
                }
        }
 
        // Méthode qui permet de mettre à jour de façon artificielle le GPS.
        // Dans un cas réel, on utiliserait les valeurs retournées par les capteurs.
        public void setMesures(String position, int precision)
        {
                this.position=position;
                this.precision=precision;
                notifierObservateurs();
        }
        
        // Méthode "tiré" donc c'est aux observeurs d'aller chercher les valeurs désiré grâce à un objet Gps.
        // Pour cela on trouve un accesseur en lecture pour position.
        public String getPosition()
        {
                return position;
        }
        // Un accesseur en lecture pour précision.
        public int getPrecision()
        {
                return precision;
        }
}

La classe Gps implémente l’interface Observable et peut ainsi avoir des observateurs à son écoute. La liste des observateurs à l’écoute est stockée dans un tableau dynamique en attribut. De plus, cette classe possède deux états un correspondant au positionnement et l’autre à la précision. La précision indiquera la fiabilité du positionnement (plus on utilise de satellites plus la précision est forte). Un constructeur permet d’initialiser les attributs. Les trois méthodes définies dans l’interface Observable sont implémentées. De plus, on trouve deux accesseur en lecture correspondant aux deux états de la classe. En effet, nous utiliserons la méthode « TIRER » comme présentée dans la description du pattern Observateur. Enfin la méthode setMesures(String, int) permettra de simuler la réception de valeurs par le récepteur GPS. En effet, nous n’allons pas vous obliger à acheter un récepteur GPS pour tester l’application…

Implémentation des classes AfficheResume et AfficheComplet
// Affiche un résumé en console des informations (position) du GPS.
public class AfficheResume implements Observateur
{
        // Méthode appelée automatiquement lors d'un changement d'état du GPS.
        public void actualiser(Observable o)
        {
                if(o instanceof Gps)
                {       
                        Gps g = (Gps) o;
                        System.out.println("Position : "+g.getPosition());
                }       
        }
 
}
// Affiche en console de façon complète les informations (position et précision) du GPS.
public class AfficheComplet implements Observateur
{
        // Méthode appelée automatiquement lors d'un changement d'état du GPS.
        public void actualiser(Observable o)
        {
                if(o instanceof Gps)
                {       
                        Gps g = (Gps) o;
                        System.out.println("Position : "+g.getPosition()+"  Précision : "+g.getPrecision()+"/10");
                }                     
        }
 
}

Ces deux classes implémentent la méthode actualiser(Observable) qui pour rappel est appelée automatiquement lors de changement d’états de la classe observable. Dans cette méthode on vérifie que l’observable est bien de type Gps et on utilise les accesseurs pour aller chercher le ou les états. La seule différence entre les deux classes concerne l’affichage en console de seulement la position pour AfficheResume et d’avantage d’informations (position et précision) pour AfficheComplet.

Implémentation de la classe Main
// Classe principale du projet.
public class Main
{
        // Méthode principale.
        public static void main(String[] args)
        {
                // Création de l'objet Gps observable.
                Gps g = new Gps();
                // Création de deux observeurs AfficheResume et AfficheComplet
                AfficheResume ar = new AfficheResume();
                AfficheComplet ac = new AfficheComplet();
                // On ajoute AfficheResume comme observeur de Gps.
                g.ajouterObservateur(ar);
                // On simule l'arrivée de nouvelles valeurs via des capteurs.
                g.setMesures("N 39°59°993 / W 123°00°000", 4);
                // On ajoute AfficheComplet comme observeur de Gps.
                g.ajouterObservateur(ac);
                // Nouvelle simulation d'arrivée de nouvelles valeurs via des capteurs.
                g.setMesures("N 37°48°898 / W 124°12°011", 5);
                
        }
 
}

Comme dans les autres exemples d’implémentations de patterns la classe Main correspond à la classe principale de notre programme. On commence par créer les objets suivants : Gps, AfficheResume et AfficheComplet. Puis on abonne les observateurs à l’objet de type Gps et on fait évoluer régulièrement les mesures.

Résultat en console
Position : N 39°59°993 / W 123°00°000
Position : N 37°48°898 / W 124°12°011
Position : N 37°48°898 / W 124°12°011  Précision : 5/10

Une fois l’observateur AfficheResume abonné à l’observable Gps il est tenu au courant des changements d’état. Ce n’est pas le cas de l’objet de type AfficheComplet car même s’il implémente l’interface Observateur l’objet n’est pas ajouté à la liste des observateurs de Gps. Une fois cette opération effectuée on constate que les deux observateurs sont bien tenus informés des changements d’état de Gps.


Implémentation d’une solution basée sur l’API Java



Après une implémentation personnelle du pattern Observateur en Java voyons ce que propose l’API. Celle-ci facilite l’utilisation de ce pattern en fournissant une classe Observable et une interface Observer. L’interface Observer équivaut à notre interface Observateur la méthode appelée automatiquement lors de changement d’état étant update(Observable, Object). On remarque que la signature de cette méthode permet, aux choix, de « TIRER » ou « POUSSER ». La classe Observable diffère un peu de notre interface Observable précédente. Tout d’abord il s’agit d’une classe ce qui est inconvénient notable, cependant cela permet d’y insérer une partie de l’implémentation. Voyons les modifications que cela engendre dans le modèle et l’implémentation de notre exemple.

Implémentation de la classe Gps
// Classe représentant un GPS (appareil permettant de connaître sa position).
public class Gps extends Observable
{
        private String position;// Position du GPS.
        private int precision;// Précision accordé à cette position (suivant le nombre de satellite utilisés).
        
        // Constructeur.
        public Gps()
        {
                position="inconnue";
                precision=0;
        }
        
        // Méthode permettant de notifier tous les observateurs lors d'un changement d'état du GPS.
        public void notifierObservateurs()
        {
                setChanged();// Méthode de l'API.
                notifyObservers();// Egalement une méthode de l'API.
        }
 
        // Méthode qui permet de mettre à jour de façon artificielle le GPS.
        // Dans un cas réel, on utiliserait les valeurs retournées par les capteurs.
        public void setMesures(String position, int precision)
        {
                this.position=position;
                this.precision=precision;
                notifierObservateurs();
        }
        
        // Méthode "tiré" donc c'est aux observeurs d'aller chercher les valeurs désiré grâce à un objet Gps.
        // Pour cela on trouve un accesseur en lecture pour position.
        public String getPosition()
        {
                return position;
        }
        // Un accesseur en lecture pour précision.
        public int getPrecision()
        {
                return precision;
        }
}

Le code de la classe Gps est bien plus léger en utilisant l’API. On retrouve, le constructeur, les deux accesseurs en lecture pour les états, la méthode setMesures(String, int) et la méthode notifierObservateurs(). Cependant l’implémentation de cette dernière a été modifiée puisqu’elle appelle seulement deux méthodes de l’API : setChanged() et notifyObservers(). La méthode setChanged() permet de mettre à vrai un attribut de Observable. Et c’est seulement si cette attribut est à vrai que notifyObservers() informera les observateurs de changement (puis modifiera cet attribut à faux). L’intérêt de ce procéder est de contrôler les appels aux observateurs afin d’avoir la possibilité de ne pas les appeler pour un changement d’état insignifiant (position déplacée d’un millimètre par exemple).

Implémentation des classes AfficheResume et AfficheComplet
// Affiche un résumé en console des informations (position) du GPS.
public class AfficheResume implements Observer
{
        // Méthode appelée automatiquement lors d'un changement d'état du GPS.
        public void update(Observable o, Object obj)
        {
                if(o instanceof Gps)
                {       
                        Gps g = (Gps) o;
                        System.out.println("Position : "+g.getPosition());
                }       
        }
 
}
//Affiche en console de façon complète les informations (position et précision) du GPS.
public class AfficheComplet implements Observer
{
        // Méthode appelée automatiquement lors d'un changement d'état du GPS.
        public void update(Observable o, Object obj)
        {
                if(o instanceof Gps)
                {       
                        Gps g = (Gps) o;
                        System.out.println("Position : "+g.getPosition()+"  Précision : "+g.getPrecision()+"/10");
                }                     
        }
 
}

Le code est très similaire à l’exemple précédent. Seul le nom et la signature de la méthode a été modifiée. Comme énoncé précédemment cette nouvelle signature met en place les deux modes de récupération de l’état « TIRER » ou « POUSSER ».

Implémentation de la classe Main
// Classe principale du projet observeurApi.
public class Main
{
        // Méthode principale.
        public static void main(String[] args)
        {
                // Création de l'objet Gps observable.
                Gps g = new Gps();
                // Création de deux observeurs AfficheResume et AfficheComplet
                AfficheResume ar = new AfficheResume();
                AfficheComplet ac = new AfficheComplet();
                // On ajoute AfficheResume comme observeur de Gps.
                g.addObserver(ar);
                // On simule l'arrivée de nouvelles valeurs via des capteurs.
                g.setMesures("N 39°59°993 / W 123°00°000", 4);
                // On ajoute AfficheComplet comme observeur de Gps.
                g.addObserver(ac);
                // Nouvelle simulation d'arrivée de nouvelles valeurs via des capteurs.
                g.setMesures("N 37°48°898 / W 124°12°011", 5);
        }
}

Seul le nom des méthodes appelées est modifié dans la classe Main. On garde volontairement le même exemple.

Résultat en console
Position : N 39°59°993 / W 123°00°000
Position : N 37°48°898 / W 124°12°011  Précision : 5/10
Position : N 37°48°898 / W 124°12°011

On constate que le résultat est le même que précédemment. Ce qui est logique puisque nous avons pris le même exemple et nous avons utilisé dans les deux cas le pattern Observateur.


Conclusion

Maintenant que nous savons implémenter notre propre pattern Observateur et utiliser celui de l’API nous pouvons nous demander lequel est-il préférable d’utiliser ? La réponse est : ça dépend des cas. Si on souhaite une implémentation rapide on peut utiliser l’API Java. En revanche, dans certain cas, on sera obligé de faire une implémentation personnelle du pattern Observateur. En effet, dans l’API, Observable est une classe. Il faut donc que la classe qui souhaite gérer des observateurs puisse hériter de celle-ci. Or l’héritage multiple n’existe pas en Java, donc ceci n’est possible que si cette classe n’hérite pas déjà d’une autre classe. On comprend alors l’utilité de définir une interface plutôt qu’une classe.

Pour résumer l’implémentation du pattern Observateur est souvent utilisé et peu complexe à mettre en œuvre. Il fait donc partie des patterns indispensables à connaître, que l’on rencontre régulièrement lors de développement.


Code source

Observateur en Java : positionnement via un GPS

Commentaires

Observateur en Java (positionnement via un GPS)

Soumis par Anonyme le Lundi 11/01/2010 06:05

Bonjour,
Merci pour ce tuto, au début j’avais du mal avec GPS (poutant GPS est fait pour ne pas se perdre !!!) mais après ça allait ;-)
Une petite question : quelle est l’utilité du paramètre obj dans la méthode update ?
javadoc dit : an argument passed to the notifyObservers method
Pourra-tu l’expliquer un peu s’il te plaît et si possible un exemple concret (si ce n’est pas trop demandé)
Merci par avance
passionnéeDuJava

Observateur en Java (positionnement via un GPS)

Soumis par Mathieu G. le Jeudi 14/01/2010 00:14

Bonjour,
Je n’avais pas détaillé ce paramètre car je ne l’utilisait pas dans mon exemple mais ta question est intéressante.
Tout d’abord si tu souhaites utiliser ce paramètre il faut utiliser la méthode notifyObservers(Object arg) d’Observable et non notifyObservers(). Ce paramètre permet de passer directement un état aux observateurs. Dans notre exemple cela peut correspondre à la valeur de l’attribut position.
En fait lors de l’utilisation de la méthode update() d’Observable si on utilise le premier paramètre o on applique la méthode TIRER (c’est les observateurs qui doivent aller lire le ou les états). En revanche si on utilise la méthode update() avec le deuxième paramètre obj on applique la méthode POUSSER car ce paramètre contient la valeur de l’état directement accessible aux Observateurs.
Les concepteurs de Java ont pensés à faciliter l’utilisation des 2 méthodes. Pour un exemple d’utilisation de la deuxième méthode : https://en.wikipedia.org/wiki/Observer_pattern
J’espère que mon explication te sera utile.

Observateur en Java (positionnement via un GPS)

Soumis par Anonyme le Vendredi 15/01/2010 11:40

Bonjour Mathieu,
Merci de ta réponse, tu expliques tellement bien que j’espère trouver encore d’autres tuto sur ton site ;-)
Finalement est-ce qu’on peut dire :
s’il y a

notifyObservers(Object quoi)
on aura
public void update (Observable qui, Object quoi)
Une dernière petite question : est-ce que le mot observable ici est une lapsus ou c’est moi qui n’ai pas compris :
En fait lors de l’utilisation de la méthode update() d’Observable ...
bonne journée
passionnéeDuJava

Observateur en Java (positionnement via un GPS)

Soumis par Anonyme le Vendredi 15/01/2010 16:08

Bonjour Mathieu,
C’est toujours la passionnéeDeJava qui pose une nouvelles petite question ;-)
Pour la version où nous créons une interface observable (à notre goût), si j’ai bien compris on se fait aider par la classe Observable du java et on crée nos méthodes (de notre interface) comme les méthodes de la classe java.
Donc notre interface ne dépends aucune interface ni une classe (elle ne fait ni extends ni implements).
et justement est-ce qu’on peut créer une interface et la faire héritée de la classe Observable pour avoir toutes ses possibilités ??
ex :

monInterface extends Observable .......

Observateur en Java (positionnement via un GPS)

Soumis par Mathieu G. le Dimanche 17/01/2010 22:01

Bonjour ma fidèle lectrice ;)
Je réponds à tes 2 posts d’un coup.
Oui si on utilise notifyObservers(Object quoi) on doit normalement implémenter la méthode update (Observable qui, Object quoi).
Il s’agit en effet d’un lapsus puisque la signature de la méthode update() est définie dans l’interface Observer (l’heure de mon précédent message te permettra sans doute de comprendre l’origine de ce lapsus ;) ).
Les deux exemples sont dissociés. Le premier exemple permet de montrer que l’on peut implémenter soit même le pattern Observateur. Le second explique l’utilisation de l’API Java qui nous facilite la vie (avec toutefois l’inconvénient que Observable est une classe et non une interface).
Quand à ta proposition de créer une interface et la faire hériter de la classe Observable cela n’est pas possible. Parce qu’une interface définie uniquement les signatures des méthodes (jamais le corps des méthodes) et elle ne peut donc jamais hériter d’une classe (qui elle est susceptible de posséder le corps des méthodes).

Observateur en Java (positionnement via un GPS)

Soumis par Anonyme le Lundi 18/01/2010 00:49

Bonjour Mathieu,
Merci beaucoup de ta réponse si détaillée.
passionnéeDuJava ;-)

Observateur en Java (positionnement via un GPS)

Soumis par Arnaud le Vendredi 25/06/2010 00:41

Bonsoir, je ne sais pas si je déterre le sujet (posté il y a 3ans) mais j’aurais une petite question sur ce design pattern.
j’explique d’abord mon contexte ca sera plus facile à comprendre.
mon programme gère l’arbitrage d’une course à pied, je releve des flashs de coureurs qui passent sur des points de passage ; quand un point est franchi je crée un Evenement, pour cela j’ai créé une classe evenement (générique) et plusieurs classes d’événements qui héritent de celle-ci (par exemple : evtDepart, evtArrivée ...).
je "pousse" mes événements aux observeurs donc on a la méthode update (Evenement e) et je voudrais que les observeurs puissent choisir quels types d’événement ils traitent. pour cela j’ai surchargé la méthode update dans les classes observeurs.
donc on se retrouve dans l’interface avec update ( Evenement e ) et dans les classe qui l’implémente update (evtDepart ), ... Seulement je ne veut faire qu’une seule méthode notifieObserveurs() dans le sujet donc j’utilise l’événement générique dans cette méthode, et les observeurs voit arriver un objet Evenement et ne font plus de traitement spécialisé...
le seul moyen que j’ai trouvé est de faire une methode notifieObserveurs() pour chaque type d’événement a traiter dans le sujet,
y aurait-il un moyen de garder le bénéfice de la surcharge de update en envoyant un evenement générique (qui ne l’est pas en fin de compte) ?

Observateur en Java (positionnement via un GPS)

Soumis par Mathieu G. le Mardi 29/06/2010 21:02

Bonjour Arnaud,
Difficile de te répondre précisément sans connaître l’implémentation de ton application. Mais de façon générale, si les événements non rien avoir entre eux il vaut mieux avoir plusieurs méthodes notifierObservateurs() (que l’on pourra nommer de façon explicite).
Sinon il faut que les Observateurs puissent identifier s’ils doivent traiter l’évènement reçu (par exemple en appelant une méthode sur celui-ci). Il faut tout de même faire attention de ne pas notifier trop souvent des Observateurs qui finalement non rien à traiter pour des raisons de performances.
Si malgré cette explication le doute persiste je te propose de m’envoyer par mail (l’adresse est notée sur le site) les sources de ton application ou du moins un extrait fonctionnel sur l’utilisation de ce pattern.
A bientôt.

Observateur en Java (positionnement via un GPS)

Soumis par Arnaud le Jeudi 01/07/2010 11:07

Merci de votre reponse, j’ai deja plusieur méthodes notifierObserveur(), mais justement je n’en voulais qu’une. Je pensais que la redifinition marcherait....
J’ai un diagramme de cette partie de l’architecture. je n’ai pas les sources pour le moment car le projet et finit (et je l’ai rendu avc plusieurs notifierObserveurs() ). Mais cette question me tracasse.... (ou je suis fou de faire du dev pendant les vacances =D ). Je vous envoie ce que j’ai et j’essairais de récuperrer les sources si besoin est.
merci encore de votre réponse.
Arnaud

Observateur en Java (positionnement via un GPS)

Soumis par Mathieu G. le Jeudi 05/08/2010 21:34

Bonjour,
Après quelques échanges par mail j’ai proposé à Arnaud d’utiliser l’opérateur instanceof en Java ce qui lui a permis de résoudre sa problématique. Pour rappel, cet opérateur retourne vrai si l’objet est bien une instance directe ou indirecte de la classe (ou interface) testée.
En espérant que cela pourra être utile à d’autres, mais à utiliser avec précaution...

Observateur en Java (positionnement via un GPS)

Soumis par ould le Mardi 12/04/2011 12:34

merci pour ce tuto excellent je comprends enfin le design observateur.
merciiiiiiii

Observateur en Java (positionnement via un GPS)

Soumis par Mathieu G. le Dimanche 17/04/2011 21:43

Merci pour ton commentaire :)

Excellent!

Soumis par Possum4D le Dimanche 23/09/2012 11:28

Le format de ce cours est exceptionnel. Comme beaucoup, j'attends la suite de tes articles sur les dp avec impatience.
Bon courage et merci
Possum4D

Excellent!

Soumis par Mathieu G. le Mardi 25/09/2012 22:39

Merci pour tes encouragements.
Je suis toujours aussi motivé pour écrire de nouveaux articles mais je vais avoir peu de temps disponible ces prochaines semaines.
A bientôt.

Observateur en Java

Soumis par heri le Vendredi 28/12/2012 12:53

merci Mathieu G. pour cet article qui est très clair :)
j'attends impatiemment d'autres :)

Observateur en Java

Soumis par Mathieu G. le Mardi 08/01/2013 22:01

Merci pour d'avoir pris le temps de poster votre message, cela me motive pour les prochains articles :)


Ajouter un commentaire

Saisissez la lettre figurant en majuscule dans le mot "desigN" ?

Tous les champs sont obligatoires.
La publication n'est pas instantanée, elle intervient après un processus de modération.