Sujet du TD 2  2425 (v2)

Objectifs

Ce TD est appelé TD2 non pas parce-que c'est le deuxième, mais parce qu’il appartient à la séquence 2; il peut être découpé en TD2.1 et TD2.2.     
Il a pour but de vous entraîner à écrire sur papier (comme en contrôle) une classe complète, avec ses attributs et ses méthodes.  La javadoc n’est pas demandée, mais le code doit être parfaitement compilable et respecter toutes les conventions de nommage et de codage exposées dans le cours 1.          
Pour chaque exercice, lire les 2 parties Q(uestion) et R(éponse)  avant de commencer à le résoudre.

Si vous tenez à écrire le code demandé directement sur ordinateur, vous ne devez utiliser ni BlueJ ni un éditeur de texte avec coloration syntaxique, ni un compilateur : un simple Bloc-Notes !

Quelques rappels

- Une classe (ex: Cercle) possède des attributs (ex: aDiametre) d'un certain type (ex: int).

- Un constructeur permet d'initialiser les attributs, soit à des valeurs fixées dans ses instructions, soit à des valeurs qu'on lui passe en paramètres (entre les parenthèses). Il ne comporte aucun mot entre public et son nom (qui doit être identique à celui de la classe). Il est appelé automatiquement à la création de chaque objet de sa classe.

- Une procédure (ex: vaDroite) ne retourne rien, ce qui est indiqué dans sa définition par le mot void entre public et le nom de la procédure.

- Mais on peut aussi définir une fonction qui retourne toujours une et une seule valeur, dont le type doit être précisé entre public et le nom de la fonction. Ses instructions se terminent toujours par un return suivi de la valeur qu'elle retourne.

- Lorsqu'il n'y a pas de paramètres, tant la définition que l'appel d'une fonction, d'une procédure, ou d'un constructeur se termine quand-même obligatoirement par ().

- On notera que la classe Cercle possède ainsi deux méthodes de même nom : Cercle. Ceci ne crée pas d'ambiguïté car ces 2 méthodes ont des signatures différentes. Cette possibilité de Java est appelée surcharge.

- a = b % c;  met dans a le reste de la division entière de b par c, alors que a = b / c;  met dans a le quotient de la division entière de b par c.    est l’opérateur souvent appelé « modulo ».

- le + appliqué à une String permet de lui concaténer la représentation textuelle de n’importe quel objet ou nombre.

- ne pas confondre l’affectation a = b; qui change la valeur de a, avec la comparaison a == b qui vaut vrai ou faux, mais qui ne modifie rien.

- l’instruction   SI condition_est_vraie ALORS instructions_1 SINON instructions_2 FINSI   se traduit par :   
if (condition) { instructions_1 } else { instructions_2 }

- Un appel récursif survient quand une méthode s'appelle elle-même (avec une valeur de paramètre qui a changé).

- Il est possible d’afficher les valeurs de vos variables avec System.out.println(…); 
Cela fonctionne pour tous les types primitifs et pour les
String (pour l’instant, pas pour les types qu’on crée).

- Ci-après, le code java (corrigé) de MaPremiereClasse pour pouvoir vous en inspirer.

* dans le sujet suivant signale un apport de connaissance, à apprendre comme les encadrés rouges du cours !

Ma première classe

public class MaPremiereClasse // nom peu intéressant pour une vraie classe

{

  // ### Attributs ###

  private int     aPremierAttribut; // noms

  private boolean aDeuxiemeAttribut; // peu intéressants

  private String  aTroisiemeAttribut; // pour de vrais attributs

 

  // ### Constructeurs ###

  /**

   * Construit toujours le meme objet : 2, true, "exemple"

   */

  public MaPremiereClasse()

  {

      this.aPremierAttribut   = 2;

      this.aDeuxiemeAttribut  = true;

      this.aTroisiemeAttribut = "exemple";

  } // MaPremiereClasse()

 

  /**

   * Constructeur naturel

   * @param pI entier  pour initialiser aPremierAttribut

   * @param pB booleen pour initialiser aDeuxiemeAttribut

   * @param pS chaine  pour initialiser aTroisiemeAttribut

   */

  public MaPremiereClasse( final int pI, final boolean pB, final String pS )

  {                     // Comment justifier ces noms de paramètres ?

      this.aPremierAttribut   = pI;

      this.aDeuxiemeAttribut  = pB;

      this.aTroisiemeAttribut = pS;

  } // MaPremiereClasse()

 

  // ### Accesseurs ###

  public int     getPremierAttribut()   { return this.aPremierAttribut;   }

  public boolean getDeuxiemeAttribut()  { return this.aDeuxiemeAttribut;  }

  public String  getTroisiemeAttribut() { return this.aTroisiemeAttribut; }

 

  // ### Modificateurs ###

  public void setPremierAttribut(   final int     pPremierAttribut )

      { this.aPremierAttribut   = pPremierAttribut; }

  public void setDeuxiemeAttribut(  final boolean pDeuxiemeAttribut )

      { this.aDeuxiemeAttribut  = pDeuxiemeAttribut; }

  public void setTroisiemeAttribut( final String  pTroisiemeAttribut )

      { this.aTroisiemeAttribut = pTroisiemeAttribut; }

 

  // ### Autres methodes ###

  /**

   * Un exemple de procedure (qui ne retourne rien)

   */

  public void procedure()

  {

    String vEspace = " "; // variable locale

    System.out.println( "La procedure” + vEspace + "s'est bien executee." );

    // l’instruction System.out.println(x); affiche x dans la fenêtre Terminal *

    return; // facultative dans une procédure

  } // procedure()

 

  /**

   * Un exemple de fonction booleenne

   * @return toujours true

   */

  public boolean fonction()

      { return true; }

} // MaPremiereClasse

I. La classe

Q: On souhaite pouvoir représenter et manipuler un nombre rationnel, c'est-à-dire une fraction (le rapport entre deux entiers). Créer la classe nécessaire (vide, pour l'instant).

R: Le mot souligné dans la question ci-dessus paraît adapté comme nom de classe. Donc, écrire en java le début et la fin de cette classe (attention aux conventions de nommage). Entre les deux, nous écrirons ensuite tous les attributs et toutes les méthodes.

II. Les attributs

Q: Un nombre rationnel est composé de deux nombres entiers appelés numérateur et dénominateur. Créer (c'est-à-dire déclarer) les attributs nécessaires.

R: Les mots soulignés dans la question ci-dessus paraissent adaptés comme noms d'attributs (pour aller plus vite, on peut choisir de ne conserver que les 3 premières lettres). Le type de ces attributs étant précisé dans la question, écrire en java la déclaration de ces 2 attributs (attention aux conventions de nommage).

III. Les constructeurs

Q: Écrire le constructeur naturel (qui possède autant de paramètres qu'il y a d'attributs *).

R: Il s'écrit toujours de la même façon, tant sa signature (attention aux conventions de nommage) que son corps.
    v1) On supposera d’abord que le dénominateur n’est jamais nul.          
    v2) Comme nous ne savons pas comment signaler une erreur dans un constructeur, créer le rationnel 0 (avec le dénominateur le plus simple possible) lorsqu’on lui passe un dénominateur nul.

Q: Écrire un deuxième constructeur (quelconque), à un seul paramètre, que l'on utilisera pour créer un nombre rationnel lorsqu'il a la particularité d'être égal à un nombre entier.  Donc à quoi correspond ce paramètre ?      
 (par exemple, quel est le plus simple rationnel – numérateur & dénominateur – qui vaut 12 ?).

R: Dans ce cas, nul besoin de préciser en paramètre la valeur du dénominateur : nous la connaissons déjà !      Par exemple, si le rationnel vaut 5, que valent son numérateur et son dénominateur (au plus simple) ?  
    v1) Comme le rôle d'un constructeur est toujours le même, une première idée serait de recopier et adapter les instructions du constructeur naturel que nous avons déjà écrit (faites-le !).           
    v2) Mais comme la duplication de code est l'ennemi public numéro un en programmation, nous REMPLACERONS ces 2 instructions par une seule, celle qui permet à un constructeur d'en appeler un autre dans la même classe (en lui passant bien les paramètres qu'il attend) :      
  this( liste_des_valeurs_que_le_constructeur_appelé_attend_en_paramètres ); * 

Veuillez noter qu’il y a toujours exactement 7 caractères en plus des éventuels paramètres, pas plus pas moins.
Attention !
Cette instruction ne peut être écrite qu'en première instruction d'un constructeur. *

IV. Les accesseurs et les modificateurs *

En anglais, Ils sont appelés getters and setters car les premiers commencent toujours par get et les seconds toujours par set. Ces méthodes sont utilisées principalement quand on est à l’extérieur de la classe.

Q: Écrire les accesseurs (pour accéder -- en lecture -- à un attribut).

R: Vous savez combien ils sont (d'après le nombre d'attributs), comment ils s'appellent (d'après le nom de l'attribut), si ce sont des fonctions ou procédures (doit retourner la valeur de l'attribut), quels sont leurs paramètres (aucune information supplémentaire n'est nécessaire pour accomplir leur tâche), et quelle instruction ils contiennent (rôle simple et unique). Si ce n'est pas le cas, demandez à un intervenant (c'est la différence avec un contrôle ;-)

Q: Écrire les modificateurs (pour modifier la valeur d'un attribut) en refusant de mettre 0 dans un dénominateur.

R: Vous savez combien ils sont (d'après le nombre d'attributs), comment ils s'appellent (d'après le nom de l'attribut), si ce sont des fonctions ou procédures (doit modifier la valeur de l'attribut sans retourner de résultat), quels sont leurs paramètres (il faut bien préciser la valeur qu'on souhaite mettre dans l'attribut), et quelle instruction ils contiennent (rôle simple et unique). Si ce n'est pas le cas, demandez à un intervenant (c'est là que se situe la différence avec un contrôle ...)

V. Deux fonctions à définir dans la plupart des classes

Q: Écrire la fonction toString qui retourne une String représentant au mieux le rationnel courant ; elle n'a besoin d'aucun paramètre. Cette fonction sera évidemment appelée à chaque fois qu'on aura besoin d'afficher un nombre rationnel, comme par exemple 3 fois si on souhaite afficher cette égalité : 1/3 + 1/6 = 1/2.  
Attention ! Cette fonction ne doit en aucun cas modifier le rationnel courant.

R: v1) Écrire une première version simple qui ne tient compte d'aucun cas particulier, c'est-à-dire qui se contente de retourner le numérateur suivi d'un / suivi du dénominateur.      
+ Il faut donc ici construire une chaîne de caractères à partir de plusieurs morceaux (le numérateur, le /, et le dénominateur). En java, l'opérateur de concaténation (mise bout à bout) de chaînes de caractères est tout simplement le +. En outre, si vous écrivez uneChaine+unEntier, alors unEntier est automatiquement converti en chaîne de caractères avant d'être concaténé à uneChaine.            "" (chaîne vide) est différente de " " (un espace).
     v2) Ajouter 2 cas particuliers (qui conduisent à la même action) : a) le numérateur est nul (donc peu importe
           le dénominateur)
et b) le dénominateur vaut 1 (donc inutile de l'afficher).
     v3) Ajouter enfin ce qu'il faut pour qu'un dénominateur ne soit jamais négatif (on préfère
-1/3 à 1/-3).

[… TD 2.1] " [TD 2.2 …] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Q: Écrire la fonction booléenne egal qui retourne Vrai ou Faux selon que le rationnel (passé en unique paramètre) est égal ou non au rationnel courant (un produit en croix ?).   
A noter : on peut utiliser le type
Rationnel à l’intérieur de la classe Rationnel. *

R: Le type retourné, le nom et le paramètre sont indiqués dans la question. Il faut maintenant déterminer à quelle condition on peut affirmer que 2 rationnels sont égaux : ce n'est pas simplement « leurs numérateurs sont égaux et leurs dénominateurs sont égaux » car 1/3 est égal à 2/6 par exemple. Le produit croisé semble ici le plus simple à écrire (1x6 est bien égal à 2x3). Mais au fait, peut-on bien accéder aux numérateurs et dénominateurs des 2 rationnels à comparer ? La réponse est oui :  
+ La protection assurée par private ne se situe pas autour de chaque objet, mais autour de la classe, ce qui permet à une méthode appelée sur un objet d'accéder aux attributs privés d'un autre objet de cette même classe.

R2 : Si vous avez utilisé un if , réfléchissez à la façon dont on pourrait totalement s’en passer !?
            (ce qu’on appelle une condition est une simple valeur booléenne…)

VI. Autres méthodes

Q: Écrire la fonction add qui retournera le rationnel égal à la somme du rationnel courant avec le rationnel passé en paramètre (sans simplification). Par exemple, l'ajout d' 1/3 à 1/6 retournera le nouveau rationnel 9/18.

R: Le type retourné, le nom et le paramètre sont indiqués dans la question. Il faut évidemment calculer le numérateur et le dénominateur du rationnel résultat de cette addition, et retourner un nouveau rationnel créé avec ces 2 valeurs calculées. Aide : Vous vous rappelez de new Cercle() dans BlueJ ?

Q: Écrire une procédure simplification du Rationnel courant, qui par exemple, transformera 12/18 en 2/3. A-t-elle besoin d’informations supplémentaires ?

Exemple d’exécution :
(6,15) à (15,6)
à (6,3) à (3,0) à 3

 
R: Pour ne pas avoir à faire de boucle, on divisera simplement en haut et en bas par le PGCD (il faut créer cette fonction auxiliaire dans la classe Rationnel).  Si la simplification fonctionne bien, il peut être intéressant de l'appeler automatiquement à la fin de add et aussi à la fin du constructeur naturel.         
Donc créer et utiliser une fonction récursive (sans boucle !) pour calculer le PGCD :     
-
pgcd( a, 0 )
à a      
- pgcd( a, b )
à pgcd( b, a modulo b )     Que donne cette formule si b>a ? Essayez !

 Exemple d’exécution : (6,15) → (15,6) → (6,3) → (3,0) → 3 

VII. Autres classes

Q: Comment tester/utiliser/vérifier le bon fonctionnement de la nouvelle classe qui vient d'être écrite ci-dessus ?

R: Créer une classe Utilisation sans attributs avec une seule procédure essai sans aucun paramètre qui créera des rationnels dont vous choisirez judicieusement les valeurs, et qui testera toutes les méthodes.