Série 8:
Héritage et base du polymorphisme

Buts

Dans cette série, vous apprendrez à utiliser la notion de hiérarchie pour organiser vos classes. Comme pour la série précédente, il est important de comprendre chaque exercice et d'en étudier le corrigé pour une bonne prise en main des concepts de POO.


Exercice 1: Figures géométriques (Héritage, Niveau 0)

a

Introduction

Le but de cet exercice est d'illustrer la notion d'héritage en utilisant une hiérarchie de figures géométriques. Pour simplifier, nous laisserons tous les accès de méthodes publics dans cet exercice.

Le fichier FiguresGeometriques.java, comporte les définitions des classes Rectangle et Cercle ci-dessous (similaire à ce que vous avez dans le code dans la série 6), ainsi qu'une ébauche de main:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
class FiguresGeometriques {

    public static void main(String[] args) {
        // A COMPLETER
    }
}

class Rectangle {

    private double largeur;
    private double longueur;

    public Rectangle(double largeur, double longueur) {
        this.largeur = largeur;
        this.longueur = longueur;
    }

    public double surface() {
        return largeur * longueur;
    }

    public double getLongueur() {
        return longueur;
    }

    public double getLargeur() {
        return largeur;
    }

    public void setLargeur(double l) {
        largeur = l;
    }

    public void setLongueur(double l) {
        longueur = l;
    }
}

class Cercle {
    // abscisse du centre
    private double x;
    // ordonnée du centre
    private double y;
    private double rayon;

    public Cercle(double x, double y, double r) {
        this.x = x;
        this.y = y;
        rayon = r;
    }

    public void affiche() {
        System.out.println("centre = (" + x + ", " + y + ")");
    }

    public double getX() {
        return x;
    }

    public double getY() {
        return y;
    }

    public void setCentre(double x, double y) {
        this.x = x;
        this.y = y;
    }

    public double surface() {
        return Math.PI * rayon * rayon;
    }

    public boolean estInterieur(double x, double y) {
        return (((x - this.x) * (x - this.x) + (y - this.y) * (y - this.y))
                    <= rayon * rayon);
    }

    public double getRayon() {
        return rayon;
    }

    public void setRayon(double r) {
        if (r < 0.0) r = 0.0;
        rayon = r;
    }
}

Commençons par y ajouter la classe RectangleColore qui hérite de Rectangle.
Cette classe doit simplement avoir un attribut de plus, couleur, disons de type int.

[Essayez de le faire par vous même avant de regarder la solution qui suit]

Ceci se fait très simplement :

  1. la classe RectangleColore hérite de Rectangle :

    class RectangleColore extends Rectangle {
    }

  2. et a un attribut de plus:

    class RectangleColore extends Rectangle {
        private int couleur;
    }

Pour rendre notre nouvelle classe plus réaliste (et utilisable) ajoutons lui un constructeur qui fera appel au constructeur de la classe parente :

    public RectangleColore(double larg, double longueur, int c){
        super(larg, longueur); 
        couleur = c;
    }

Testez avec ce main simple :

public static void  main(String[] args) {
    RectangleColore r = new RectangleColore(4.3, 12.5, 4);
    System.out.println(r.getLargeur());
}

(que vous aurez complété dans le fichier fourni)

Continuez en définissant la classe Figure. Celle-ci contient:

Faites ensuite hériter les classes Cercle et Rectangle de la classe Figure.

[Essayez de le faire par vous même avant de regarder la solution qui suit]

Pour la classe Figure, rien de particulier, et il suffit de déplacer les définitions correspondantes de la classe Cercle à la classe Figure (donc les supprimer de Cercle).

Puis on ajoute la méthode affiche et le constructeur  :

class FiguresGeometriques {

    public static void main(String[] args) {
        RectangleColore r = new RectangleColore(1.2,3.4,12.3,43.2,4);
        r.affiche();

        Cercle c = new Cercle (2.3, 4.5, 12.2);
        c.affiche();
    }
}

class Figure {
    // abscisse du centre
    private double x;
    // ordonnee du centre
    private double y;

    public Figure(double x , double y){
        this.x = x;
        this.y = y;
    }

    public void affiche() {
        System.out.println("centre = (" +  x + ", " + y + ")");
    }

    public double getX() {

Venons-en à l'héritage proprement dit. Il faut spécifier les liens d'héritage aux deux classes concernées.

class Rectangle extends Figure { ...
class Cercle extends Figure { ...

puis modifier les constructeurs de manière appropriée :

    //constructeur de la classe Cercle
    public Cercle(double x, double y, double r) {
        super(x,y);
        rayon=r;
    }
...
    //constructeur de la classe Rectangle
    public Rectangle(double x, double y,double larg, double longueur){
        super(x,y);
        this.largeur = larg;
        this.longueur = longueur;
    }
...
    //constructeur de la classe RectangleColore
    public RectangleColore(double x, double y,double larg, double longueur, int c){
        super(x,y,larg, longueur);
        couleur = c;
    }

Pour la méthode d'affichage, supposons que l'on souhaite que, pour la classe Cercle, la méthode affiche également le rayon (mais continue d'afficher le centre).

Cela se fait en définissant dans la classe Cercle une méthode affiche utilisant la méthode masquée affiche de la super classe Figure :

class Cercle extends Figure {
 ... 
    public void affiche(){
        super.affiche();
        System.out.println("rayon = " + rayon);
    }
..
}

On peut bien sûr faire de même avec les classes Rectangle et RectangleColore :

..
//dans la classe Rectangle
    public void affiche(){
        super.affiche();
        System.out.println("Largeur = " + largeur);
        System.out.println("Longeur = " + longueur);
    }
..
//dans la classe RectangleColore
    public void affiche(){
        super.affiche();
        System.out.println("Couleur = " + couleur);
    }

La méthode estInterieur de la classe Cercle doit être modifiée car l'accès direct aux attributs x et y, désormais hérités de Figure, n'est plus possible. Il faut passer par des getters.

Testez le programme avec la méthode main suivante :

    public static void main(String [] args) {
        RectangleColore r = new RectangleColore(1.2,3.4,12.3,43.2,4);
        r.affiche();

        Cercle c = new Cercle (2.3, 4.5, 12.2);
        c.affiche();
    }

(modifiez celle déjà utilisée). [ici le code complet]

Exercice 2: MOOC (cours en ligne) (MOOC, Niveau 1)

Cette semaine vous devez visionner les vidéos de la semaine 3 du MOOC. Pour bien assimiler ce contenu, il est recommandé de faire les quiz des mêmes semaines: Quiz «Héritage».


Exercice 3: Que de véhicules (Héritage, Niveau 1)

3.1 La classe Vehicule

Dans un fichier GestionVehicules.java, définissez une classe Vehicule qui a pour attributs des informations valables pour tout type de véhicule :

Définissez un constructeur prenant en paramètre les trois attributs correspondant à la marque, la date d'achat et le prix d'achat. Le prix courant sera calculé plus tard.

Définissez une méthode publique void affiche() qui affiche l'état de l'instance, c'est-à-dire la valeur de ses attributs.

3.2 La classe Voiture et la classe Avion

Définissez deux classes Voiture et Avion, héritant de la classe Vehicule et ayant les attributs supplémentaires suivants :

Définissez, pour chacune de ces classes, un constructeur permettant l'initialisation explicite de l'ensemble des attributs, ainsi qu'une méthode affichant la valeur des attributs. Constructeurs et méthodes d'affichage devront utiliser les méthodes appropriées de la classe parente !

Encore des méthodes

Ajoutez une méthode void calculePrix(int anneActuelle) dans la classe Vehicule qui, à ce niveau, fixe le prix courant au prix d'achat moins 1% par année (entre la date d'achat et la date actuelle).

Redéfinissez cette méthode dans les deux sous-classes Voiture et Avion de sorte à calculer le prix courant en fonction de certains critères, et mettre à jour l'attribut correspondant au prix courant :

Le prix doit rester positif (donc s'il est négatif, on le met à 0).

La méthode main

Afin de tester les méthodes implémentées ci-dessus, complétez la méthode main comme suit :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class GestionVehicules {
    // Pour repréesenter l'année courante:
    // Il est aussi bien sur possible d'utiliser la classe Date
    // pour r'ecupérer cette information automatiquement.
    private static int ANNEE_ACTUELLE = 2020;   
        
    public static void main(String[] args) {
        Voiture[] garage = new Voiture[3];
        Avion[] hangar = new Avion[2];

        garage[0] = new Voiture("Peugeot", 2005, 147325.79, 2.5, 5, 180.0, 12000);
        garage[1] = new Voiture("Porsche", 1999, 250000.00, 6.5, 2, 280.0, 81320);
        garage[2] = new Voiture("Fiat", 2001, 7327.30, 1.6, 3, 65.0, 3000);

        hangar[0] = new Avion("Cessna", 1982, 1230673.90, "HELICES", 250);
        hangar[1] = new Avion("Nain Connu", 1993, 4321098.00, "REACTION", 1300);

        for (int i = 0; i < garage.length; i++) {
            garage[i].calculePrix(ANNEE_ACTUELLE);
            garage[i].affiche();
        }

        for (int i = 0; i < hangar.length; i++) {
            hangar[i].calculePrix(ANNEE_ACTUELLE);
            hangar[i].affiche();
        }
    }
}

Exemple d'exécution :

---- Voiture ----
marque : Peugeot, date d'achat : 2005, prix actuel : 95761.76350000002
2.5 litres, 5 portes, 180.0 CV, 12000.0 km.
 ---- Voiture ----
marque : Porsche, date d'achat : 1999, prix actuel : 94999.99999999997
6.5 litres, 2 portes, 280.0 CV, 81320.0 km.
 ---- Voiture ----
marque : Fiat, date d'achat : 2001, prix actuel : 3810.1960000000004
1.6 litres, 3 portes, 65.0 CV, 3000.0 km.
 ---- Avion à HELICES----
marque : Cessna, date d'achat : 1982, prix actuel : 923005.4249999999
250 heures de vol.
 ---- Avion à REACTION----
marque : Nain Connu, date d'achat : 1993, prix actuel : 3759355.26
1300 heures de vol.

Amélioration du main

Améliorez la méthode main ci-dessus en tenant compte du fait que tous les véhicules ont un type commun: Vehicule.

Exercice 4: Héritage de variables (Héritage, Niveau 1)

Le programme Erreur ci-dessous définit les classes E1, E2, E3, E4, E5 et Erreur. Pour chacune des 5 instructions d'affichage dans la méthode main, indiquez si l'instruction est correcte ou fausse. Compilez et corrigez le programme pour vérifier vos réponses.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Erreur {
    
    public static void main(String args[]) {
        E1 x = new E1();
        E2 y = new E2();
        E3 z = new E3();
        E4 v = new E4();
        E5 w = new E5();
        System.out.println(x.a);  // Correct ou faux ?
        System.out.println(y.c);  // Correct ou faux ?
        System.out.println(z.b);  // Correct ou faux ?
        System.out.println(v.c);  // Correct ou faux ?
        System.out.println(w.a);  // Correct ou faux ?
    }
    
}

class E1 {
    int a = 1;
}

class E2 extends E1 {
    int b = 2;
}

class E3 extends E2 {
    int c = 3;
}

class E4 extends E1 {
    int d = 4;
}

class E5 extends E4 {
    int e = 5;
}



Exercice 5: EPFLiens (Héritage, Niveau 2)

Ecrivez un programme Direction.java qui aide la direction à gérer les EPFLiens (les personnes qui fréquentent l'EPFL). Votre programme doit être orienté objets et sans duplication de code inutile.

On aimerait stocker, dans un seul tableau, des informations sur 4 catégories d'EPFLiens:
  1. Les secrétaires. Chaque secrétaire est décrit(e) par son nom, l'année où il/elle est arrivée à l'EPFL, ainsi que par le nom de son laboratoire ou institut. Chaque secréraire a un salaire.

  2. Les enseignants. Chaque enseignant est décrit par son nom, l'année où il est arrivé à l'EPFL, le nom de son laboratoire ou institut, le nom de la section dans laquelle il enseigne (nous supposons que chaque enseignant s'occupe d'une seule section). Chaque enseignant a un salaire.

  3. Les étudiants d'échange. Chaque étudiant d'échange est décrit par son nom, l'année où il est arrivé à l'EPFL, le nom de la section dans laquelle il est inscrit, ainsi que le nom de son université d'origine.

  4. Les étudiants réguliers, sont ceux qui suivent la totalité de leurs études à l'EPFL. Chaque étudiant régulier est décrit par son nom, l'année où il est arrivé à l'EPFL, le nom de la section dans laquelle il est inscrit et son résultat au propé I. Pour les étudiants en 1ère année, une estimation de son résultat au propé I est utilisée.
Remplissez votre tableau par autant d'objets (personnes) que vous voulez. Voici quelques exemples:
  1. L'étudiant régulier "Gaston Peutimide", inscrit en section de systèmes de communication en 2020. Son résultat au propé I est 6.0.

  2. L'étudiant régulier "Yvan Rattrapeur", inscrit en section de systèmes de communication en 2016. Il a obtenu un résultat de 2.5 au propé I.

  3. L'étudiant d'échange "Björn Borgue", inscrit en section d'informatique en 2018. Son université d'origine s'appelle "KTH".

  4. L'enseignant "Mathieu Matheu", engagé au Laboratoire des Mathématiques Extrêmement Pures (LMEP) en 2015. Il a un salaire de 10000 francs par mois et il enseigne à la section de physique.

  5. La secrétaire "Sophie Scribona", engagée au Laboratoire des Machines à Taper (LMT) en 2005. Elle a un salaire de 5000 francs par mois.
Prévoyez les opérations suivantes dans votre programme:
  1. Affichage du nombre d'EPFLiens, dont le nombre d'étudiants.

  2. Affichage du nombre d'années moyen pendant lesquelles les personnes enregistrées ont fréquenté l'EPFL.

  3. Affichage des informations enregistrées sur chaque personne.
Exemple d'exécution:

Parmi les 5 EPFLiens, 3 sont des etudiants.
Ils sont ici depuis en moyenne 6.2 ans
Liste des EPFLiens: 
Etudiant regulier:
   Nom : Gaston Peutimide
   Annee : 2020
   Section : SSC
   Moyenne : 6.0
Etudiant regulier:
   Nom : Yvan Rattrapeur
   Annee : 2016
   Section : SSC
   Moyenne : 2.5
Etudiant d'echange:
   Nom : Bjorn Borgue
   Annee : 2018
   Section : Informatique
   Uni d'origine : KTH
Enseignant:
   Nom : Mathieu Matheu
   Annee : 2015
   Laboratoire : LMEP
   Salaire : 10000
   Section d'enseignement : Physique
Secretaire:
   Nom : Sophie Scribona
   Annee : 2005
   Laboratoire : LMT
   Salaire : 5000



Exercice 6: Boîte aux lettres (Héritage, algorithme, Niveau 2)

Il s’agit dans cet exercice de proposer une conception modélisant une boîtes aux lettres en Java orienté-objet.

Une boîtes aux lettres recueille des lettres, des colis et des publicités.

Une lettre est caractérisée par :

Un colis est caractérisé par :

Une publicité est caractérisée par :

Voici les règles utilisées pour affranchir le courrier :

  1. en mode d’expédition normal :
    – le montant nécessaire pour affranchir une lettre dépend de son format et de son poids :
    Formule : montant = tarif de base + 1.0 * poids (kilos), où le tarif de base pour une lettre "A4" est de 2.50, et 3.50 pour une lettre "A3"
    – le montant nécessaire pour affranchir une publicité dépend de son poids :
    Formule : montant = 5.0 * poids (kilos)
    – le montant nécessaire pour affranchir un colis dépend de son poids et de son volume :
    Formule : montant = 0.25 * volume (litres) + poids (kilos) * 1.0;
  2. en mode d’expédition express : les montants précédents sont doublés, quelque soit le type de courrier;
  3. seul le courrier valide est affranchi;
  4. un courrier n’est pas valide si l’adresse de destination est vide;
  5. un colis n’est pas valide si son adresse de destination est vide ou s’il dépasse un volume de 50 litres.


Les trois méthodes principales liées à la boîte aux lettre sont les suivantes :

1. une méthode affranchir() permettant d'associer à chaque courrier de la boîte, le montant nécessaire pour l'affranchir. Cette méthode retournera le montant total d'affranchissement du courrier de la boîte.
2. une méthode courriersInvalides() calculant et retournant le nombre de courriers invalides présents
dans la boîte aux lettres.

3. une méthode afficher() affichant le contenu de la boîte aux lettre (on indiquera alors quels courriers sont invalides).

Sur papier, commencez par dessiner une hiérarchie de classes permettant de mettre en oeuvre la conception suggérée en tenant compte des contraintes mentionnées. Vous spécifierez dans votre diagramme les classes, les attributs et les entêtes des méthodes (sans les corps). Les contraintes suivantes devront être respectées :

  1. Votre conception doit être faite de sorte à ce qu'aucune des méthodes requises n'ait besoin de faire de test sur la nature de l'objet auquel elle s'applique.
  2. Les classes doivent fournir toutes les méthodes qui leur sont nécessaires.
  3. Une classe ne comportera que les méthodes/attributs qui lui sont spécifique.
  4. Les modificateurs d'accès devront être clairement spécifiés.
  5. Vos classes doivent éviter de dupliquer inutilement des méthodes ou des attributs et elle seront compatibles avec le programme principal fourni dans le fichier Poste.java.

Implémentez ensuite le programme résultant de votre conception dans le fichier Poste.java

Avec le programme principal fourni, vous devriez avoir une exécution telle que (le prix indique le coùt d'affranchissement):


Le montant total d'affranchissement est de 47.4
Lettre
        Poids : 200.0 grammes
        Express : oui
        Destination : Chemin des Acacias 28, 1009 Pully
        Prix : 7.4 CHF
        Format : A3

Lettre
(Courrier  invalide)
        Poids : 800.0 grammes
        Express : non
        Destination : 
        Prix : 0.0 CHF
        Format : A4

Publicité
        Poids : 1500.0 grammes
        Express : oui
        Destination : Les Moilles  13A, 1913 Saillon
        Prix : 15.0 CHF

Publicité
(Courrier  invalide)
        Poids : 3000.0 grammes
        Express : non
        Destination : 
        Prix : 0.0 CHF

Colis
        Poids : 5000.0 grammes
        Express : oui
        Destination : Grand rue 18, 1950 Sion
        Prix : 25.0 CHF
        Volume : 30.0 litres

Colis
(Courrier  invalide)
        Poids : 3000.0 grammes
        Express : oui
        Destination : Chemin des fleurs 48, 2800 Delemont
        Prix : 0.0 CHF
        Volume : 70.0 litres

La boite contient 3 courriers invalides



Exercice 7: Devoir du MOOC (MOOC, Niveau 2)

Les devoirs du MOOC, même s'il ne sont pas notés pour vous, sont un bon moyen de vous entraîner aussi. Vous pouvez les soumettre à un correcteur automatique (autant de fois que vous le souhaitez) et recevoir un feedback. Cette semaine vous pouvez faire le devoir de la semaines 2 (encapsulation, constructeurs) et celui de la semaine 3 (héritage).


Dernière mise à jour: 08/11/2024  (Revision: 1.2)