Travaux Pratiques n°3

Lectures préalables :  
Thèmes du TP :
  • La classe Object
  • L'héritage
  • Les tableaux
  • Le package java.util
  • interface , abstract
  • classe interne

 

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.

Question 1. (Object)

Il s'agit de modifier la Pile du TP2 (votre meilleure version) pour qu'elle stocke des objets (java.lang.Object) plutôt que des entiers.

Cette classe java.lang.Object est la racine (ou la mère) de toute classe Java.

(i) Proposez une nouvelle version de la classe Pile.

(ii) Compilez la classe TestPile ci-dessous, et vérifier l'affichage produit.

public class TestPile { // fichier TestPile.java
    public static void main( String[] args ) {
    Pile p1 = new Pile( 5 );
    Pile p2 = new Pile( 10 );
    // p1 est ici une pile de polygones réguliers (voir le source de cette classe
    p1.empiler( new PolygoneRegulier(4,100) );
    p1.empiler( "polygone" );
    p1.empiler( new Integer(100) );
    System.out.println( " la p1 = " + p1 ); // Quel est le résultat ?

    p2.empiler( new Integer(1000) );
    p1.empiler( p2 );
    System.out.println( " la p1 = " + p1 ); // Quel est le résultat ?

    // p1.empiler(p1);   // Attention aux conséquences par la suite !
    // System.out.println( " la p1 = " + p1 ); 
1) décommentez les 2 lignes précédentes; que se passe-t-il ?
2) recommentez le println; que se passe-t-il ?
  }
}

(iii) Proposez une nouvelle version de l'IHM ApplettePile qui permet d'empiler "n'importe quoi".
(repartez de la meilleure version dont vous disposez, si possible TP2.3.iii; si vous n'avez réalisé aucune applette, vous pouvez prendre cette applette minimale comme point de départ)

Vous devriez obtenir le comportement suivant :

ApplettePile qui peut empiler des entiers, des réels, des mots, ...

En réalité, qu'est-ce qui est stocké dans la pile ?

(iv) Une nouvelle documentation regroupant toutes les classes utilisées en (i) (ii) (iii) est demandée.


Question 2. (interface)

(i)

(ii) Modifiez la classe Pile pour qu'elle implémente cette interface.

(iii) Modifiez la classe TestPile de la question précédente pour utiliser PileI et pour pouvoir tester la nouvelle fonction.

(iv) Une nouvelle documentation regroupant toutes les classes utilisées en (i) (ii) (iii) est demandée.


Question 3. (délégation)

  (i) Proposez une autre implémentation de l'interface PileI en utilisant une classe prédéfinie.

AIDE :

  • La classe java.util.Stack citée dans le cadre au début de ce sujet peut vous être d'un grand secours.
  • La nouvelle classe PileStack possèdera une instance de la classe Stack, comme le suggère l'extrait de code suivant :
          import java.util.Stack;
          public class PileStack implements PileI { // fichier PileStack.java
            private Stack stk; // La PileStack est implémentée par délégation
            ...
          } 
  • Cette approche est nommée par Mark Grand le "Pattern delegation". Elle consiste à définir une donnée d'instance d'une classe, ensuite utilisée dans les méthodes.
    Ce pattern réalise une interface entre le client de la classe et l'implémentation effective par une classe de service. Il permet un couplage moins fort entre les deux classes que si l'une héritait de l'autre.
  • Attention ! La classe Stack n'est pas bornée ...

(ii) Modifiez la classe TestPile pour qu'elle utilise la classe PileStack plutôt que la classe Pile.

(iii) Modifiez PileStack et TestPile pour respecter la spécification initiale de la Pile du TP2 qui précisait qu'il s'agissait d'une pile bornée.

(iv) Une nouvelle documentation regroupant toutes les classes utilisées en (i) (ii) (iii) est demandée.

Question 4. (tests globaux)

(i) Proposez une classe de test comparant vos 2 implémentations d'une Pile avec celle de Sun.

  1. Pour cela, cliquez sur le bouton New Class... et choisissez Unit Test.
  2. Créez un objet de la classe Stack (que nous considérerons comme notre implémentation de référence) en utilisant le choix Use Library Class... du menu Tools.
  3. Créez un objet Pile et un objet PileStack tous les deux de taille 3.

    Vous devriez voir à peu près ça :  

  4. Générez le code correspondant à la création de ces 3 objets à l'aide du choix Object Bench to Text Fixture dans le menu contextuel de la classe de test.
  5. Créez une méthode de test à l'aide du choix Create Test Method... du menu contextuel : si vous tapez Vide, cela créera la méthode testVide.
    Vous pouvez par exemple enregistrer les actions suivantes : vérifier qu'isEmpty() est vrai, empiler, vérifier qu'isEmpty() est faux, dépiler, et vérifier qu'isEmpty() est vrai.
  6. Mais comme nous souhaitons une comparaison avec l'implémentation de référence, il nous faut remplacer les true et false par des appels "parallèles"   aux méthodes de la classe Stack.
    Vous devriez obtenir à peu près ceci :
        assertEquals(stack1.isEmpty(), pile1.estVide());
        pile1.empiler("a");  stack1.push("a");
        assertEquals(stack1.isEmpty(), pile1.estVide());
        assertEquals(stack1.pop(), pile1.depiler()); // teste aussi empiler() et depiler()
        assertEquals(stack1.isEmpty(), pile1.estVide());
    
  7. Il reste à régler le problème des exceptions. Le plus simple serait de les transmettre au niveau supérieur par des throws. Mais n'est-il pas plus intéressant de vérifier qu'une exception est bien lancée quand elle doit l'être, et qu'elle ne l'est pas quand elle ne doit pas l'être ?
    Une idée peut être de mettre assertEquals( false, true ); après les instructions qui auraient dû déclencher une exception, ou bien dans la partie catch si une telle exception n'aurait pas dû être déclenchée. D'autre part, quand une exception doit être déclenchée, il faut vérifier dans la partie catch que l'exception est bien du bon type, en écrivant par exemple :
      try
        {
        ...
        }
      catch( Exception e)
        { assertEquals( true, e instanceof PilePleineException ); }
    
    Remarques :
    assertTrue( X ); est plus élégant que assertEquals( true, X );
    fail(); est plus élégant que assertEquals( false, true );

  8. Nous avons donc un bon test de la méthode estVide() pour la classe Pile, mais faut-il tout dupliquer pour obtenir le même test pour la classe PileStack ?
    Évidemment non, il suffit de paramétrer la méthode avec PileI, et de l'appeler une fois avec une Pile et une fois avec une PileStack.
    Le fait d'ajouter un paramètre à testVide() ne permet plus à BlueJ de la reconnaître en tant que méthode de test appelée automatiquement par Test All... et apparaissant dans le menu contextuel.
    Eh bien, cela nous arrange car cela permet de créer 2 méthodes de test (sans paramètre !) qui appelleront chacune testVide() avec un paramètre différent, et seules ces 2 fonctions seront appelées par Test All...

AIDE :

  • Les classes de test JUnit sont documentées ici et sont basées sur le document © 2002 Robert A. Ballance intitulé "JUnit: Unit Testing Framework".
  • Les objets Test (et TestSuite) sont associés aux classes à tester par leur nom (par exemple, un test de la classe Name.java se nommera NameTest.java); les deux se retrouvent dans le même paquetage.
  • BlueJ découvre automatiquement (par introspection) les méthodes de test de votre classe Test et génère la TestSuite correspondante. L'ensemble de ces méthodes forme un objet TestSuite appelé "Suite de tests de non-régression".
  • Chaque appel d'une méthode de test est précédé d'un appel à setUp() qui met en place la "configuration de départ" ("fixture" en anglais), et suivi d'un appel à tearDown() qui "fait le ménage".

(ii) Complétez sur le même modèle ce premier test par d'autres méthodes de test consacrées aux autres méthodes de PileI sauf est_Pleine() (voir partie optionnelle).

(iii) Commentez vos tests dans la page web du rapport pour qu'une personne découvrant cet exercice sache quels tests sont et ne sont pas effectués sans être obligée de lire le code de la classe de test.

Optionnel* :

Question 5. (compléments)

(i) Modifiez la classe PileStack pour que l'instruction p1.empiler(p1); n'ait plus de conséquences fâcheuses et testez la modification avec TestPile.

(ii) Utilisez la classe PileStack dans l'applette de la question 1.ii et ajoutez un bouton "sommet" pour pouvoir tester cette fonction.

(iii) Dupliquez la classe PileStack de la question précédente pour qu'elle délègue à une autre classe que Stack (la classe Vector citée dans le cadre au début de ce sujet ou une autre).

Puis incorporez cette nouvelle classe PileVector dans l'applette précédente et remarquez le peu de répercussions qu'a eu ce changement complet d'implémentation.

(iv) Une nouvelle documentation regroupant toutes les classes utilisées en (iii) est demandée.

(v) Ajouter sur le même modèle que la question 4.i une méthode de test pour estPleine().

Pour cela, il faut que Stack() se comporte comme une pile bornée. Quelques suggestions :
  • dans la méthode SetUp, remplacer stack1=new Stack(); par stack1=createStack(3); et définissez la méthode CreateStack() pour que stack1 n'ait bien que 3 cases (voir la documentation de la classe Stack et de Vector dont elle hérite).
  • dans les assertEquals() destinés à tester estPleine(), remplacer stack1.isFull() (qui n'existe pas !) par isFull(stack1) et définissez la méthode isFull() pour qu'elle retourne bien vrai ou faux dans les bons cas.

* Optionnel signifie qu'il n'est pas obligatoire de "rendre" ces questions sur la page web dont vous avez enregistré l'URL, mais vous devez essayer de traiter toutes ces questions pendant le TP, et elles peuvent donner lieu à un bonus sur la note de TP.


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

Énumération

(i) Proposez une nouvelle version de la classe PileStack qui utilise une Enumeration pour implémenter sa méthode toString().

L'objectif est de respecter un "standard" de parcours de tous les éléments d'une collection de données : la méthode elements() nous retourne une Enumeration spécifique à la collection de données, sur laquelle on peut appeler les méthodes hasMoreElements() et nextElement().

Pour cela, ajoutez la classe interne PileIterateur selon le modèle ci-dessous, ainsi que la méthode public java.util.Enumeration elements(); fournie ci-dessous.

   public class PileStack implements PileI {
     ... // données de la Pile 
     private class PileIterateur implements java.util.Enumeration {
       private ...
       public PileIterateur() { ... }
       public Object nextElement() { ... }
       public boolean hasMoreElements() { ... }
     } // PileIterateur
     public java.util.Enumeration elements() {
       return new PileIterateur();
     } // elements() 
     public String toString() { 
       java.util.Enumeration e = this.elements();
       ... 
     } // toString()
   } // PileStack

AIDE :

Une possibilité est de cloner la Stack dans PileIterateur et de dépiler au fur et à mesure tous les éléments.

(ii) Fournir la nouvelle documentation.

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


Annexe 1.(Source de la classe PolygoneRegulier)

/**
 * TP3.1.ii: PolygoneRegulier.java
 * @author Denis BUREAU
 */

public class PolygoneRegulier
  {
  private int nombreDeCotes;
  private int longueurDuCote;

  PolygoneRegulier( int N, int L )
    {
    nombreDeCotes  = N;
    longueurDuCote = L;
    } // PolygoneRegulier()

  int perimetre()
    {
    return nombreDeCotes * longueurDuCote;
    } // perimetre()

  int surface()
    {
    return (int)( nombreDeCotes / 4.0
                  * Math.pow( longueurDuCote, 2.0 )
                  * atan( Math.PI / nombreDeCotes ) );
    } // surface()

  public boolean equals( Object obj )
    {
    if ( !( obj instanceof PolygoneRegulier ) )
      return false;
    PolygoneRegulier p = (PolygoneRegulier)obj;
    return ( p.nombreDeCotes == nombreDeCotes )
           && ( p.longueurDuCote == longueurDuCote );
    } // equals()

  public String toString()
    {
    return "<" + nombreDeCotes + "," + longueurDuCote + ">";
    } // tostring()

  private static double atan( double x )
    {
    return Math.cos( x ) / Math.sin( x );
    } // atan()
  } // class PolygoneRegulier

Annexe 2.(Source d'une applette minimale pour la Pile)

/**
 * TP2.3.i : ApplettePile.java
 */
import java.applet.*;
import java.awt.*;
import java.awt.event.*;

/**
 *  Applette sans exceptions
 *  @author JMD&DB
 */
public class ApplettePile extends Applet implements ActionListener
  {
  private TextField donnee  = new TextField( 7 );
  private TextField sommet  = new TextField( 7 );
  private Label     contenu = new Label( "[ ]" );

  private Pile p = new Pile( 4 );

  public void init()
    {
    Button boutonEmpiler = new Button( "Empiler" );
    Button boutonDepiler = new Button( "Depiler" );

    Panel enHaut = new Panel();
    enHaut.add( donnee );
    enHaut.add( boutonEmpiler );
    enHaut.add( boutonDepiler );
    enHaut.add( sommet );
    setLayout( new BorderLayout(5,5) );
    add( "North", enHaut );
    add( "Center", contenu );
    enHaut.setBackground( Color.black );
    boutonEmpiler.addActionListener( this );
    boutonDepiler.addActionListener( this );
    } // init()

  public void actionPerformed( ActionEvent ae )
    {
    if ( ae.getActionCommand().equals( "Empiler" ) )
      p.empiler( Integer.parseInt( donnee.getText() ) );
    else
      sommet.setText( Integer.toString( p.depiler() ) );
    contenu.setText( p.toString() );
    } // actionPerformed()
  } // ApplettePile

--> Dernière mise à jour le par DB