Travaux Pratiques n°4

Lectures préalables :  
Thèmes du TP :
  • Le pattern Observateur
  • A.W.T.
  • Le modèle MVC
  • Le pattern Adapteur

 

Rappels :
Vous devez fournir des copies d'écran des diagrammes de classe de BlueJ, et la documentation générée doit faire apparaître vos commentaires sur toutes les classes, les méthodes, les paramètres, et les valeurs de retour.

Attention !
Les questions signalées par *** doivent être lues pour la compréhension de la suite du sujet, mais sont optionnelles, c'est-à-dire peuvent ne pas être rendues pour gagner du temps.


Question 1. (Test du pattern Observateur/Observé)

(i) Le paquetage java.util propose la classe Observable et l'interface Observer (lire leur javadoc).

On souhaite vérifier le fonctionnement de ces entités Java qui implémentent le pattern Observateur/Observé vu en cours.

Pour cela, il faut concevoir une classe de test qui vérifie notamment que lors d'une notification, TOUS les observateurs ont été informés, que les arguments ont bien été transmis, et que le notifiant est correct.
Le projet BlueJ aura la particularité de n'être constitué que d'une classe de test.

Un point de départ pour cette classe de test est donné à l'annexe 1.

(ii) Ajoutez des méthodes de test


Question 2. (Observer)

(i) On souhaite maintenant mettre en oeuvre le pattern Observateur dans une petite application permettant d'observer le comportement d'un entier et d'un réel.

Notamment, les observateurs doivent être prévenus lors de chaque changement d'état d'un nombre.
Prendre connaissance des classes suivantes :

  1. // NombreObservable.java
    public abstract class NombreObservable extends java.util.Observable
    {
      abstract public double valeur();
      abstract public void inc();
    } // NombreObservable
      
  2. // Entier.java
    public class Entier extends NombreObservable
      {
      private int entier;
    
      public Entier( int e ) { entier = e; }
      public double valeur() { return (double)entier; }
      public void inc() { entier++; /* à compléter */ }
      } // Entier
      
  3. Écrire la classe Flottant selon le même modèle.
  4. // ObservateurDEntier.java
      public class ObservateurDEntier implements java.util.Observer
      {
      private char avant, apres;
      public ObservateurDEntier( char av, char ap ) { avant = av;  apres = ap; }
      public void update( java.util.Observable obs, Object obj )
        {
        Entier e = (Entier)obs;
        System.out.println( "ObservateurDEntier : " + avant + (int)e.valeur() + apres );
        } // update()
      } // ObservateurDEntier
      
  5. Écrire les classes ObservateurDeFlottant et ObservateurDeNO selon le même modèle.
    Le paramètre Object de la méthode update() n'est pas utilisé dans cet exemple.
  6. // TestObservateur.java
    public class TestObservateur
      {
      public static void main( String[] args )
        {
        Entier e = new Entier( 1 );
        // e a 2 observateurs : o1, o2
        ObservateurDEntier o1 = new ObservateurDEntier( '(', ')' );
        ObservateurDEntier o2 = new ObservateurDEntier( '[', ']' );
        e.addObserver( o1 );  e.addObserver( o2 );
        
        Flottant f = new Flottant( 10F );
        // f a 1 observateur o3
        ObservateurDeFlottant o3 = new ObservateurDeFlottant( '{', '}' );
        f.addObserver( o3 );
    
        // un observateur commun
        ObservateurDeNO o4 = new ObservateurDeNO( '*', '*' );
        e.addObserver( o4 );  f.addObserver( o4 );
        
        e.inc();  e.inc();  f.inc();  f.inc();
        } // main()
      } // TestObservateur
      

AIDE :

Voici le diagramme de classes visé :

(ii) Commentez les résultats obtenus suite à l'exécution de la méthode main() de la classe TestObservateur.

(iii) Fournir la documentation regroupant toutes les classes des questions (i) et (ii).


Question 3. (AWT)

(i) Le paquetage java.awt (Abstract Window Toolkit) permet notamment de gérer les événements engendrés par des boutons dans les applettes en respectant le pattern observateur :

Soit l'applette suivante (voir le source à compléter) :

Le bouton A a 3 observateurs. Le bouton B a un seul observateur (commun avec A).
Générez l'applette avec BlueJ, puis exécutez-la avec appletviewer sous DOS pour voir les messages.

(ii) *** Ajouter à cette applette (et au bouton B) la gestion des événements souris.

Vous devriez obtenir le comportement suivant :

Le bouton A a 3 observateurs. Le bouton B a un seul observateur (commun avec A).
L'applette et le bouton B ont un observateur de souris complet.
Générez l'applette avec BlueJ, puis exécutez-la avec appletviewer pour voir les messages dans la fenêtre DOS.

Pour cela, ajouter dans l'applette la classe ObservateurDeSouris qui doit implémenter l'interface java.awt.event.MouseListener.

AIDE :

Pour respecter l'interface MouseListener, il faut implémenter toutes les méthodes mouse...ed() (avec ... = Enter, Exit, Press, Releas, Click).
Le contenu de chaque méthode peut être simplement :
System.out.println( "mouse ...ed at " + e.getX() + "," + e.getY() );

(iii) *** Modifier la classe ObservateurDeSouris pour qu'elle hérite de la classe java.awt.event.MouseAdapter plutôt que d'implémenter l'interface java.awt.event.MouseListener.

Quelles en sont les conséquences pour la classe ObservateurDeSouris ? Ne s'intéresser qu'à l'événement clic.

Vous devriez obtenir le comportement suivant :

Le bouton A a 3 observateurs. Le bouton B a un seul observateur (commun avec A).
L'applette et le bouton B ont un observateur de clic de souris.
Générez l'applette avec BlueJ, puis exécutez-la avec appletviewer pour voir les messages dans la fenêtre DOS.

AIDE :

Il s'agit du pattern Adapteur qui ici simplifie grandement l'écriture de la classe ObservateurDeSouris lorsque l'on ne s'intéresse pas à tous les événements possibles.

(iv) Fournir la documentation regroupant toutes les classes de la question (iii).


Question 4. (MVC)

(i) Soit l'applette suivante :

Cette applette fonctionne selon le principe MVC (Modèle, Vue, Contrôleur) :

Pour cela, la classe Compteur implémente le Modèle (voir le source à compléter) et l'applette IHMCompteur implémente à la fois la Vue et le Contrôleur (voir le source à compléter).

Contraintes :

  • La valeur du compteur ne doit jamais être négative.
  • La valeur du compteur ne doit jamais dépasser la valeur maximum fixée lors de sa création.
  • Chaque modification de l'état interne du compteur engendre le "réveil" de l'observateur/contrôleur.

(ii) La Vue et le Contrôleur sont maintenant dans deux classes distinctes afin de mieux refléter le Modèle MVC.

Développer une nouvelle architecture comme suit :

(iii) Fournir la documentation regroupant toutes les classes de la question (ii).


HORS TP ** :  si vous voulez aller plus loin ...

Observateurs "à la main"

(i) Soit la situation suivante :

Comprendre et tester les classes suivantes :

  1. Une classe utilitaire représentant l'origine de l'événement
    public class Source
      {
      private Object obj;
    
      public Source( Object obj ) { this.obj = obj; }
      public Object getSource() { return obj; }
      } // Source
      
  2. Une classe abstraite représentant n'importe quel nombre observable qui gère une liste d'observateurs à l'aide d'une table de hachage (même si ce n'est pas nécessaire pour ce tp, des explications sur le principe du hachage se trouvent ici et ).
    // NombreObservable.java
    public abstract class NombreObservable
      {
      private java.util.Hashtable table = new java.util.Hashtable();
        
      abstract public double valeur();
    
      abstract public void inc();
    
      public void attache( Observateur o )
        { if ( ! table.contains(o) )  table.put( o, o ); }
    
      public void detache( Observateur o )
        { table.remove( o ); }
    
      public void notifie()
        {
        for ( java.util.Enumeration e = table.elements(); e.hasMoreElements();  )
          ( (Observateur)e.nextElement() ).miseAJour( new Source(this) );
        } // notifie()
      } // NombreObservable
      
  3. Une classe représentant un entier et qui pour être observable hérite de la classe NombreObservable
    // Entier.java
    public class Entier extends NombreObservable
      {
      private int entier;
    
      public Entier( int e ) { entier = e; }
      public double valeur() { return (double)entier; }
      public void inc() { entier++;  notifie(); }
      } // Entier
      
  4. Une interface fixant le contrat minimum pour un observateur
    // Observateur.java
    public interface Observateur
      {
      public void miseAJour( Source src );
      } // Observateur
      
  5. Une classe représentant un observateur d'entier qui respecte l'interface Observateur
    // ObservateurDEntier.java
      public class ObservateurDEntier implements Observateur
      {
      private char avant, apres;
      public ObservateurDEntier( char av, char ap ) { avant = av;  apres = ap; }
      public void miseAJour( Source src )
        {
        Entier e = (Entier)src.getSource();
        System.out.println( "ObservateurDEntier : " + avant + (int)e.valeur() + apres );
        } // miseAJour()
      } // ObservateurDEntier
      
  6. Enfin, une classe de test
    // TestObservateur.java
    public class TestObservateur
      {
      public static void main( String[] args )
        {
        Entier e = new Entier( 1 );
        // e a 2 observateurs : o1, o2
        ObservateurDEntier o1 = new ObservateurDEntier( '(', ')' );
        ObservateurDEntier o2 = new ObservateurDEntier( '[', ']' );
        e.attache( o1 );  e.attache( o2 );
    
        e.inc();  e.inc();
        } // main()
      } // TestObservateur
      

(ii) Ajouter les classes nécessaires pour pouvoir créer et observer des flottants. Ajouter dans TestObservateur un flottant, un observateur de ce flottant, et deux incrémentations de ce flottant.

AIDE :

Voici le diagramme de classes visé :

(iii) On désire maintenant ajouter un même observateur pour un entier et un flottant. Ajouter la classe ObservateurDeNO (Nombre Observable) et les tests nécessaires (dans TestObservateur).

AIDE :

Voici le diagramme de classes visé :

(iv) Fournir la documentation regroupant toutes les classes de la question (iii).

** Hors TP signifie "non pris en compte pour l'évaluation".


Annexe 1) TestObservateur.java
import java.util.Observer;
import java.util.Observable;

import java.util.Stack; // ou mieux : la pile du TP3

public class PatternObservateur extends junit.framework.TestCase
{
  private SujetObservable s1, s2;
  private Observateur     o1, o2;
  /** Piles pour sauvegarder les emetteurs et les arguments */
  private Stack           o1_emetteurs, o2_emetteurs;
  private Stack           o1_arguments, o2_arguments;
 
  public class Observateur implements Observer{
    private Stack emetteurs = new Stack();
    private Stack arguments = new Stack();
    
    public Stack emetteurs() { return emetteurs; }
    public Stack arguments() { return arguments; }
    
    public void update( Observable o, Object arg )
    {
      emetteurs.push( o.toString() );
      arguments.push( arg );
     }
   }
  
   public class SujetObservable extends Observable
   {
     private String nom;
     public SujetObservable( String nom ) { this.nom = nom; }
     public String toString() { return nom; }
     protected void setChanged() { super.setChanged(); }
   }

   protected void setUp() // throws java.lang.Exception
   {
     s1 = new SujetObservable( "s1" );
     s2 = new SujetObservable( "s2" );
    
     o1 = new Observateur();
     o1_emetteurs = o1.emetteurs();
     o1_arguments = o1.arguments();
      
     o2 = new Observateur();
     o2_emetteurs = o2.emetteurs();
     o2_arguments = o2.arguments();
   }

   protected void tearDown() // throws java.lang.Exception
   {
       // Libérez ici les ressources engagées par setUp()
   }


Annexe 2) Observateur2Boutons.java
import java.applet.Applet;
import java.awt.Button;
import java.awt.event.*;

public class Observateur2Boutons extends Applet
  {
  private Button boutonA, boutonB;
  // déclaration des 3 ObservateurDeBouton  <--- à compléter

  public void init()
    {
    boutonA = new Button( "A" );
    // enregistrement des 3 observateurs du bouton A  <--- à compléter
     
    boutonB = new Button("B");
    // enregistrement de l'observateur du bouton B  <--- à compléter
     
    add( boutonA );  add( boutonB );  // sur l'IHM
    } // init()
  } // Observateur2Boutons

public class ObservateurDeBouton implements ActionListener
  {
  public void actionPerformed( ActionEvent e )
    {
    System.out.println( "evenement bouton " + e.getActionCommand() + " : " + this );
    } // actionPerformed()
  } // ObservateurDeBouton


Annexe 3) Compteur.java
public class Compteur
  {
  private int max, val = 0;
  private ObservateurDeCompteur observateur;

  public Compteur( int max ) { this.max = max; }

  public void reset() { /* à compléter */ }
  public int getVal() { /* à compléter */ }
  public void setVal( int val ) { /* à compléter */ }
  public void inc() { /* à compléter */ }
  public void dec() { /* à compléter */ }

  public void enregistrerCommeObservateur( ObservateurDeCompteur observateur )
    {
    this.observateur = observateur;
    notifierALObservateur();
    } // enregistrerCommeObservateur()

  private void notifierALObservateur()
    {
    observateur.compteurAChange( this );
    }
  } // Compteur


// ObservateurDeCompteur.java
public interface ObservateurDeCompteur
  {
  public void compteurAChange( Compteur ctr );
  }


Annexe 4) IHMCompteur.java
import java.applet.*;
import java.awt.*;
import java.awt.event.*;

public class IHMCompteur extends Applet implements ActionListener, ObservateurDeCompteur
  {
  private Button incButton, decButton;
  private TextField valField;
  private Compteur ctr;

  public void init()
    {
    setBackground( Color.white );

    incButton = new Button( "+" );
    decButton = new Button( "-" );
    valField  = new TextField();

    Panel p = new Panel();
    p.setLayout( new BorderLayout() );
    p.add( incButton, "North" );
    p.add( decButton, "South" );

    /* à compléter */ // Enregistrer le Contrôleur comme "écouteur" pour chacun des boutons

    add( p );
    add( valField );

    ctr = new Compteur( 10 );

    /* à compléter */ // Enregistrer la Vue comme observateur
    } // init()

  public void actionPerformed( ActionEvent e ) // le Controle : appels de ctr.inc() et ctr.dec()
    {
    if ( e.getSource() == incButton )
      /* à compléter */ 
    } // actionPerformed()

  // la vue (ObservateurDeCompteur) ==> gestion de valField :
  /* à compléter */
  } // IHMCompteur