Design pattern Observateur (observer)

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

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.


Description du problème

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, lors de l’écriture de logs... 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 ?

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 ? Il est temps d’étudier le diagramme UML du pattern Observateur qui répond de manière éprouvée à cette problématique.


Diagramme UML


Définition de la solution

Le diagramme UML du pattern Observateur définit deux interfaces et deux classes. L’interface Observateur sera implémenté par toutes classes qui souhaitent avoir le rôle d’observateur. C’est le cas de la classe ObservateurConcret qui implémente la méthode actualiser(Observable). Cette méthode sera appelée automatiquement lors d’un changement d’état de la classe observée.

On trouve également une interface Observable qui devra être implémentée par les classes désireuses de posséder des observateurs. La classe ObservableConcret implémente cette interface, ce qui lui permet de tenir informer ses observateurs. Celle-ci possède en attribut un état (ou plusieurs) et un tableau d’observateurs. L’état est un attribut dont les observateurs désirent suivre l’évolution de ses valeurs. Le tableau d’observateurs correspond à la liste des observateurs qui sont à l’écoute. En effet, il ne suffit pas à une classe d’implémenter l’interface Observateur pour être à l’écoute, il faut qu’elle s’abonne à un Observable via la méthode ajouterObservateur(Observateur).

En effet, la classe ObservableConcret dispose de quatre méthodes que sont ajouterObservateur(Observateur), supprimerObservateur(Observateur), notifierObservateurs() et getEtat(). Les deux premières permettent, respectivement, d’ajouter des observateurs à l’écoute de la classe et d’en supprimer. En effet, le pattern Observateur permet de lier dynamiquement (faire une liaison lors de l’exécution du programme par opposition à lier statiquement à la compilation) des observables à des observateurs. La méthode notifierObservateurs() est appelée lorsque l’état subit un changement de valeur. Celle-ci avertit tous les observateurs de cette mise à jour. La méthode getEtat() est un simple accesseur en lecture pour l’état. En effet, les observateurs récupèrent via la méthode actualiser(Observable) un pointeur vers l’objet observé. Puis, grâce à ce pointeur, et à la méthode getEtat() il est possible d’obtenir la valeur de l’état.

Appliquons l’exemple précédent à ce diagramme UML. HeurePerso correspond à la classe ObservableConret dont l’heure et la minute courantes seraient son état. La classe AfficheHeure correspond elle à ObservateurConcret.


Approfondissement de la solution

Une des questions récurrente face à ce pattern est pourquoi ces deux interfaces ? D’ailleurs on trouve sur Internet des implémentations de ce pattern sans ces deux interfaces... Mais l’utilisation de ces interfaces permet de coupler faiblement l’observable à ses observateurs. En effet, un principe de conception est de lier des interfaces plutôt que des classes afin de pouvoir faire évoluer le modèle facilement. L’utilisation de ces deux interfaces n’est donc pas obligatoire mais elle est vivement conseillée.

Cependant, il existe une variation possible lors de l’utilisation de ce pattern. Dans la solution présentée ci dessous, une référence vers l’objet observable est mis à disposition de chaque observateur. Ainsi les observateurs peuvent l’utiliser pour appeler la méthode getEtat() et ainsi obtenir l’état de l’observable. Cette solution est nommée « TIRER » car c’est aux observateurs, une fois avertis de l’évolution, d’aller chercher l’information sur l’état. Mais il existe la solution inverse appelée « POUSSER ». Dans ce cas, on passe directement l’état actuel de l’observable dans la méthode actualiser(TypeEtat). Ainsi les observateurs disposent directement de l’état. Mais pourquoi avoir présenté la solution nommée « TIRER » plutôt que l’autre ? Parce qu’elle permet une fois de plus de lier 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 deuxième état. L’observateur souhaitant se tenir informé de ce deuxième état aura juste à appeler l’accesseur correspondant. Alors que si on « POUSSER » il faudrait changer la signature de la méthode ce qui peut s’avérer plus dommageable.


Conséquences

Un exemple que l’on rencontre souvent pour illustrer ce pattern est une représentation entre une entreprise qui diffuse un magazine et des personnes qui souhaitent s’y abonner (observateurs) et donc le recevoir régulièrement.

Le pattern observateur permet de lier de façon dynamique un observable à des observateurs. Cette solution est faiblement couplée ce qui lui permet d’évoluer facilement avec le modèle. D’ailleurs le pattern Observateur est très utilisé. Il fait partie, par exemple, des patterns indispensables pour mettre en place le modèle MVC (Modèle Vue Contrôleur) très en vogue actuellement.


Exemple d'implémentation

Design pattern Observateur en Java : positionnement via un GPS

Commentaires

Observateur

Soumis par NAL le Jeudi 09/12/2010 22:10

Bonjour, Bravo pour cet article, il est formidable ! :)

Observateur

Soumis par Mathieu G. le Vendredi 17/12/2010 20:58

Bonjour,
Merci pour ton commentaire cela me motive pour en rédiger de nouveaux. N’hésite pas à t’abonner à la newsletter j’enverrai un mail lors de la publication du prochain article.

Observateur

Soumis par informaticienne le Jeudi 23/12/2010 18:18

Je confirme,excellent tuto

Observateur

Soumis par Mathieu G. le Samedi 01/01/2011 21:57

Merci cela fait toujours plaisir :)

Observateur

Soumis par Anonyme le Lundi 03/01/2011 06:02

Article vraiment excellent ! Précis, clair, pertinent : bien meilleur que tout ce que j’ai pu lire ailleurs.
Merci beaucoup !!!

Merci !

Soumis par Toumak le Jeudi 13/01/2011 23:10

Bonsoir ! Je suis tombé sur ton site à la recherche d’infos sur différents designs patterns. Et je dois dire que j’en suis ravi ! Tes articles -et en particuliers celui-ci- sont particulièrement concis, ce qui est assez rare pour être souligné ! Encore félicitations pour ce beau travail et encore merci !

Merci !

Soumis par Mathieu G. le Samedi 22/01/2011 18:24

Bonjour,
Merci à vous 2 cela me motive pour continuer. Je vais essayer de faire aussi bien pour les prochains patterns ;)
A bientôt.

Merci !

Soumis par badr eddine le Mardi 29/03/2011 02:38

merci , je suis un éléve ingénieur vraiement j’ai compris a partir de votre article , chose que j’ai pas pu fair dans le cours .bonne continuation et j’espere que tu ecrira d’autre .

Merci !

Soumis par Mathieu G. le Lundi 04/04/2011 20:33

Merci pour tes encouragements. J’espère vraiment pouvoir écrire de nouveaux articles bientôt.
Je te souhaite bonne continuation dans tes études. A bientôt.

Observateur

Soumis par satisfaitMan le Samedi 18/06/2011 17:31

Super c’est vraiment bien écrit, bien expliqué, bien présenté. On comprend facilement du coup.

Observateur

Soumis par Mathieu G. le Dimanche 19/06/2011 21:09

Merci bien, je vais essayer de faire aussi bien pour les suivants ;)

Observateur

Soumis par Anonyme le Dimanche 13/11/2011 12:33

Bonjour,
Merci pour l’article.
N’y a-t-il cependant pas une erreur dans
« Mais pourquoi avoir présenté la solution nommée « TRIER » plutôt que l’autre ? »

Observateur

Soumis par Mathieu G. le Samedi 19/11/2011 15:17

Bonjour,
Je ne vois pas d’erreur à ce niveau. Le diagramme UML montre que la signature de la méthode actualiser prend en paramètre un objet de type Observable donc il s’agit bien de la solution TIRER qui est présenté.
N’hésitez pas à détailler vos propos si un doute subsiste.

Tres sympa...

Soumis par Yonathan le Jeudi 08/12/2011 17:44

Salut,
Je trouve ton tuto super sympa, cepandant je trouve dommage que tu n'aborde pas la question: "Pourquoi utiliser ce pattern plutot qu'un autre?".
En effet, dans ton premier chapitre 'Description du problème', je m'atendais à lire quel sont les inconvenients lorsqu'on utilise pas ce pattern pour ce type de probleme.
A part ca, vraiment super et tres bien expliqué.
Merci :)

Tres sympa...

Soumis par Mathieu G. le Mardi 13/12/2011 22:09

Merci pour ton commentaire Yonathan.
Pour répondre à la problèmatique "comment informer des objets d'un changement d'état d'un autre objet" il est recommandé d'utiliser le design pattern Observateur car il répond spécifiquement à ce besoin.
Il est vrai que je ne me suis pas attardé sur les inconvénients lorsqu'on utilise pas ce pattern (point que j'ai mis plus en avant pour d'autres patterns tel que Fabrique car je rencontre réguliérement du code répondant mal à cette problèmatique).
Pour résumer, sans l'utilisation du pattern Observateur on va être amené à coupler fortement l'Observable et les Observateurs ce qui est préjudiciable pour les évolutions futures (ajout ou modification d'un Observateur, réutilisation du code...).
En tout cas je note ton intéressante remarque pour les prochains articles.
Merci et à bientôt.

Bonjour,

Soumis par AnonymeBis le Mardi 04/09/2012 14:49

Bonjour,
La personne "Anonyme" qui a posté un commentaire le Dimanche 13/11/2011 12:33 et parlant d' "une erreur dans « Mais pourquoi avoir présenté la solution nommée « TRIER » plutôt que l’autre ? » " voulait juste préciser l'erreur de frappe au niveau du mot TRIER pour signifier TIRER.
C'est tout.

Bonjour,

Soumis par Mathieu G. le Mardi 04/09/2012 21:34

Effectivement malgré ma relecture je ne m'en étais pas aperçu.
C'est corrigé. Merci d'avoir pris le temps de m'indiquer cette erreur.
A bientôt.

Un éclaicissement please

Soumis par jt75 le Mardi 08/01/2013 14:58

Je sens bien que l'article est conçu pour avoir une bonne compréhension du concept. Cependant, je dois être un peu récalcitrant ou bouché car je n'ai pas bien compris l'exemple illustrant le discours:
à la question "quand la classe AfficheHeure devra t-elle questionner HeurePerso pour obtenir l’heure courante ? Toutes les minutes ? Toutes les secondes ? "
J'ai envie de répondre à chaque fois que c'est nécessaire, c'est à dire à chaque écriture de log.
Si par exemple un log s'écrit toutes les heures, la classe fera un appel par heure.
Si on laisse la charge à la classe HeurePerso d’informer sa classe d’affichage de ses changements de valeurs, elle fera 60 appels / heure (toutes les minutes) dont 59 inutiles.
Quelque chose m'échappe...

Un éclaicissement please

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

Oublions l'exemple de l'écriture des logs qui n'est pas le plus facile à appréhender sans détails sur le mode de fonctionnement souhaité.
Reprenons la classe HeurePerso qui est chargée de définir l'heure courante et la classe AfficheHeure qui a uniquement pour rôle d'afficher l'heure en bas à droite du bureau de votre système d'exploitation.

Si AfficheHeure appel HeurePerso toutes les 5 minutes, il ne sera pas possible d'afficher une heure précise.
Si on appel HeurePerso toutes les minutes alors il peut avoir un retard d'au maximum 59 secondes ce qui est regrétable.
Si on l'appel toutes les secondes alors on va surcharger d'appels, la plupart du temps inutiles, notre classe HeurePerso.

La meilleure solution est donc que ce soit HeurePerso qui informe AfficheHeure dès que l'heure change (c'est à dire dans ce cas prècis toutes les minutes).
Cela permet d'afficher l'heure en temps réel (ou presque) et de minimiser les appels entre ces 2 classes.
C'est le principe du Design Pattern Observateur, HeurePerso est un ObsersvableConcret et AfficherHeure un ObservateurConcret.
Un exemple d'implémentation en Java est consultable ici : https://design-patterns.fr/observateur-en-java
En espérant vous avoir éclairé.

UML

Soumis par le_futur_Bill le Samedi 28/09/2013 00:55

C´est tellement bien expliqué que je suis obligé de demander si tu n´aurais pas un cours sur UML? car t´es un as de l´explication.
Merci bien

UML

Soumis par Mathieu G. le Lundi 21/10/2013 23:14

Merci beaucoup pour ton retour.
Désolé mais je n'ai pas de cours sur UML.
Cependant, n'hésite pas à me contacter si tu as des questions précises.

Parfait

Soumis par randruc le Jeudi 01/05/2014 00:06

Simple. Clair. Bon exemple. Bon approfondissement. Bon exemple d'application.
Merci pour cet article :-)

appreciation

Soumis par mvouma le Lundi 30/11/2015 02:00

super ton article merci et continue si possible à nous en fournir bon courage

appreciation

Soumis par Mathieu G. le Vendredi 04/12/2015 22:03

Merci randruc et mvouma pour vos encouragements :)

Bravo !

Soumis par Adrien Sy le Mardi 29/03/2016 11:14

Hello, merci beaucoup pour cet article, il est très clair, bien rédigé et m'a énormément aidé !! Merci beaucoup vous êtes super !! :-)

La suite !

Soumis par Cheitan le Lundi 18/04/2016 14:48

Je viens de parcourir ce petit article sur le pattern Observateur, il y avait quelques subtilités qui m'échappaient. On attend avec impatience le pattern MVC, qui se trouve dans la droite ligne de celui-ci !
Merci pour tout le travail réalisé ici, c'est un mémo et une source d'information très pratique ! :)

observer

Soumis par ulric le Samedi 10/12/2016 01:01

juste parfait

Appreciation

Soumis par Adib HANNACHI le Dimanche 27/08/2017 13:46

Excellent tuto , super sympa :))

Explications claires

Soumis par Alice le Dimanche 15/10/2017 15:16

Article intéressant. Les explications sont claires et précises ! Continue comme ca !

Merci

Soumis par Flo le Vendredi 16/04/2021 11:24

Génial, je passe sur l'article 14 ans après et il m'a appris clairement et concisement le concept que je recherchais. Merci pour ça.


Ajouter un commentaire

Saisissez la lettre figurant en minuscule dans le mot "COMMaNDE" ?

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