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 :
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.
(6,15) à (15,6)
à
(6,3) à
(3,0) à
3
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 !
|
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.