Catégorie:
Fréquence d'utilisation:
Difficulté:

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

Diagramme UML du design pattern Observateur


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.

Exemples d'implémentations: 
Commentaires

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

Merci beaucoup !!!

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

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

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 ? »

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

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.

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

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

Simple. Clair. Bon exemple. Bon approfondissement. Bon exemple d'application.

Merci pour cet article :-)

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

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

Ajouter un commentaire