P
'
t
i
t
e
C
h
a
t
t
e
 
spacer~ I TOOK THE RED PILL Articles | Connexion
 
~Le motif Observateur

 Présentation

De nos jours, l'omniprésence des interfaces graphiques sur nos ordinateurs ne peut être contestée. De fait, les utilisateurs souhaitent bien souvent pouvoir afficher leurs données sous plusieurs formes. Les motifs de conception nous viennent à nouveau en aide pour régler un tel problème.
 Sommaire


 Introduction

Voici venu notre avant-dernière étude sur les motifs de conception décrits dans l'ouvrage du Gang Of Four. Nos attention vont donc ce mois-ci se porter sur l'un des deux derniers motifs comportementaux qui restent : l'Observateur.

Quand un utilisateur saisi des données dans une application comme un traitement de texte, son but s'avère bien souvent le traitement de celles-ci. La plupart du temps il s'agira de réaliser des graphiques récapitulatifs. Il se trouve que ce type de logiciel ne restreint pas l'utilisateur à un type de représentation graphique et propose même d'en afficher plusieurs en même temps. Ainsi après modification des données on s'attend naturellement à ce que toutes les représentations choisies se trouvent mises à jour en même temps et automatiquement.

Le rôle de ce Design Pattern se révèle de définir une interdépendance de type un à plusieurs. Cette dernière repose sur le principe de mise à jour automatique des objets dépendant d'un autre, lorsque celui change d'état. L'implémentation d'un système tel que celui du tableau induit une dissociation en nombreuses classes coopérant entre elles. Toutefois, pour satisfaire la notion de réutilisabilité, la cohérence du système ne doit pas être réalisée par l'entremise d'un couplage fort entre ces classes.


 Sujet et observateur

Pour en revenir à notre exemple de tableur, nous pouvons déduire de nos observations que tous les graphiques dépendent d'un seul et même objet, et doivent par conséquent se voir notifiés de toute modification apportée à celui-ci. Le modèle Observateur explique comment établir une relation de ce genre en se basant sur deux entités nommées sujet et observateur.

Un sujet recèle les données et peut avoir un nombre quelconque d'observateurs dépendant de lui. En réponse à une notification de modification, ces derniers récupéreront son l'état. La figure 1 présente la structure que nous devrons donner à l'implémentation. Cette structure se trouve composée de quatre éléments (nous nous reposerons sur une implémentation en Java).



Schéma 1. Structure du motif de conception Observateur.

Le premier, le Sujet, connaît les observateurs et fournit une méthode pour enregistrer ces derniers. Une version étendue de ce modèle pourra offrir une méthode de suppression des observateurs. Un Observateur pour sa part définit une interface de mise à jour pour les objets qui recevront les notifications de changement. Celles-ci se trouvent émises par le SujetConcret qui, en outre, recèle les données (l'état) traitées par les observateurs. Pour finir, la classe ObservateurConcret contient une référence vers le SujetConcret et implémente l'action de mise à jour qui sera responsable de la cohérence entre son état propre et celui du SujetConcret.

Le CD-Rom accompagnant ce magazine contient un programme dont le code source fait usage de ce Design Pattern. Il s'agit d'un éditeur de notes pour des élèves. Le logiciel permet de rentrer les moyennes des élèves et peut présenter l'ensemble sous forme d'un graphique ou sous forme d'un tableau HTML. Nous sommes typiquement dans le cadre d'une application du motif Observateur : nous possédons deux représentations à l'écran pour une source de données commune.

L'implémentation proposée se base sur trois interfaces EditorDocument, EditorSubject et EditorObserver ainsi que trois classes NotesDocument, NotesChart et NotesReport. Le sujet concret de notre exemple apparaît dans le fichier NotesDocument.java dont la classe implémente l'interface EditorDocument, qui hérite elle-même de EditorSubject. Cette dernière ajoute une seule méthode, addObserver(), à EditorDocument, qui définit une source de données et correspond donc dans notre schéma à l'interface Sujet. Sa réalisation concrète conserve la liste des observateurs dans une collection, parcourue lors de la notification d'un changement d'état. Le listing 1 présente les deux méthodes de NotesDocument héritées de EditorDocument.

//Listing 1
public void notifyObservers()
{
  for (int i = 0; i < observers.size(); i++)
    ((EditorObserver) observers.get(i)).update();
}

public boolean addObserver(EditorObserver o)
{
  if (observers == null)
    observers = new ArrayList(5);
  observers.add(o);
  return true;
}
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
Ainsi, notre éditeur invoque notifyObservers() à chaque modification des données. Cette exécution prend place au sein de la méthode update() dans la classe NotesCenterPane ainsi que le montre le listing 2. Nous pouvons également remarquer l'enregistrement des observateurs auprès du sujet. Si vous regardez de plus près le code source de la fenêtre principale nommée EditorFrame, vous découvrirez plusieurs actions sous forme de classes internes (NewAction, ModifyAction.), chargées d'invoquer la méthode update() que nous venons de découvrir.

//Listing 2
public void update()
{
  EditorDocument doc = parent.getDocument();

  if (notObserved)
  {
    doc.addObserver(chart);
    doc.addObserver(htmlView);
    notObserved = false;
  }

  doc.notifyObservers();
  // .
}
       
      
JextCopier dans Jext
Le plus intéressant réside dans les implémentations concrètes des observateurs. Celles-ci héritent toutes deux de EditorObserver et surchargent donc update(). La représentation des notes sous forme d'un tableau consulte la liste des données puis génère un code HTML placé dans un composant de type JTextPane. Dans ce cas, l'observateur fait une simple consultation et ne change pas son état (du moins pas directement dans notre code). NotesChart quant à elle réalise certains calculs avant de mettre à jour la représentation à l'écran. Ainsi, la méthode update() de cette classe exécute computeData() qui modifie l'état intrinsèque de la classe, définit par les attributs average, maxCount et distribution.

Ces différents choix dans l'architecture du logiciel ont permis de séparer efficacement les données de la représentation. Il se révèle ainsi particulièrement facile de modifier l'affichage ou le système de stockage sans pour autant bouleverser l'intégralité du code source.


 Conséquences et remarques

La mise en place d'un système de diffusion généralisé évite au sujet de connaître la nature exacte des observateurs ainsi que leur nombre. De ce fait, vous pourrez rapidement en ajouter ou en retirer. Malheureusement, l'utilisation du modèle présenté ci-dessus peut entraîner un important désagrément. Puisqu'un observateur donné ne connaît pas les autres, il peut exécuter de lourdes tâches qui pénaliseront l'efficacité de l'application. Si les mises à jours sont particulièrement fréquentes et si la chaîne d'observateurs s'avère longue, une action en apparence anodine pourra engendrer un coût excessif en terme de performance. En outre, les observateurs ne savent pas quel changement est survenu au sein du sujet. Ils se trouvent donc contraints à effectuer un important de travail de calcul, même lorsque qu'une toute petite modification a été effectuée. Vous aurez enfin sans doute constaté la similarité entre ce Design Pattern et le système Model View Controller (MVC) notamment employé par la bibliothèque Swing. N'hésitez pas à chercher par vous-même les divers modèles de conception dont fait usage l'API standard de Java.


 Téléchargements

Vous pouvez télécharger l'exemple complet.



par Romain Guy
romain.guy@jext.org
http://www.jext.org
Dernière mise à jour : 14/10/2006



 
#ProgX©2005 Mathieu GINOD - Romain GUY - Erik LOUISE