Série 9:
Polymorphisme, Typage dans une hiérarchie de classe, Modificateurs

Buts

Dans cette série, vous vous familiariserez avec le typage explicite dans des hiérarchies de classes. Vous étudierez également l'utilité des classes et méthodes abstraites dans la mise en oeuvre du polymorphisme ainsi que l'usage des modificateurs abstract et final. Vous commencerez également à étudier le matériel fourni pour le mini-projet 2. >Attention, la prise en main de ce matériel ne doit pas se faire au détriment des exercices. Il y a beaucoup d'explications mais il s'agit pour l'essentiel d'une «visite guidée» de la maquette fournie.


Exercice 1: Reprise de l'exemple des figures géométriques (Polymorphisme, Niveau 0)

Introduction

Le but de cet exercice est de reprendre un exemple de "figures géométriques" pour illustrer la notion de polymorphisme. Nous utiliserons une collection hétérogène de figures géométriques.

Dans un nouveau fichier FiguresGeometriques2.java, commencez par recopier les définitions de FiguresGeometriques.java.

On souhaite maintenant travailler avec un tableau de figures géométriques contenant à la fois des rectangles et des cercles.

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

Sans polymorphisme, nous serions obligés de créer deux tableaux différents, puisque l'on manipule deux types d'objets.

On souhaiterait maintenant plutôt pouvoir écrire notre programme principal comme suit :

class FiguresGeometriques2 {
    public static void main(String[] args) {
        Figure[] f = new Figure[3];
        f[0] = new RectangleColore(1.2, 3.4, 12.3, 43.2, 4);
        f[1] = new Cercle(2.3, 4.5, 12.2);
        f[2] = new Rectangle(1.3, 3.6, 2.3, 56.2);
        for (int i = 0; i < f.length; i++){
            f[i].affiche();
            System.out.println("La surface de cette forme est :" + f[i].surface());
        }
    }
}

et pouvoir indifféremment mettre dans notre tableau f des cercles et des rectangles. En termes informatiques, cela revient à utiliser le polymorphisme et la résolution dynamique des liens.

Comme la résolution dynamique des liens se fait automatiquement en Java, ceci ne pose en fait aucun problème particulier, et le code ci-dessus est presque correct.

Nous avons cependant un petit problème à résoudre.

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

En fait, nous souhaitons ici faire afficher aussi la surface des figures. Cependant, la méthode surface n'est pas définie dans Figure, donc si vous compilez le programme tel qu'il est, vous obtiendrez un message d'erreur du type

cannot resolve symbol
symbol  : variable surface ()
location: class Figure
			 f[i].surface());

On aimerait donc définir le calcul de surface dans la classe Figure, mais cela pose un second problème: la surface d'un cercle et celle d'un rectangle ne se calculent pas de la même manière.

La solution consiste ici à déclarer la méthode surface commme abstraite dans la classe Figure

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

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

    public abstract double surface();

    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;
    }
}

Cela ne suffit pas encore tout à fait: une classe qui contient une méthode abstraite ne peut pas être instanciée. Il faut donc également déclarer la classe Figure comme abstraite. Le code correct est donc:

abstract class Figure { ...

Voilà, notre classe Figure est maintenant utilisable comme on le souhaite.

Vous pouvez trouver ici le code complet de l'exemple.


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

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


Exercice 3: Calcul de surfaces 2 (Polymorphisme, Niveau 1)

Note : cet exercice est facultatif si vous avez programmé l'exercice de niveau 0 par vous même.

Reprenez l'exercice 3 de la série 7: Surfaces.java. Vous avez déjà converti le programme procédural en programme orienté objet, mais on voudrait maintenant améliorer le code en utilisant le polymorphisme.

Tout d'abord, créez une super-classe commune pour Cercle et Rectangle que vous appellerez par exemple Forme. Cette classe devra déclarer la méthode abstraite calculerSurface afin de forcer les sous-classes à implémenter ce calcul.

Dans la solution actuelle, Terrain contient trois instances de Rectangle et deux de Cercle, mais il devrait être capable de contenir n'importe quelle combinaison de Formes. Remplacez donc les variables d'instance r1, r2, r3, c1 et c2 par un tableau de Forme (le constructeur de la classe Terrain donnera à ce tableau une taille maximale de votre choix). Ajoutez une nouvelle méthode ajouterForme à Terrain afin de permettre à l'utilisateur d'étendre son terrain.
N'oubliez pas de modifier calculerSurfaceTotale pour prendre en compte vos modifications.

Le nouveau main devrait ressembler à ceci:

    public static void main (String[] args) {
        // Construction d'un terrain:
        Terrain t = new Terrain();

        t.ajouterForme(new Rectangle(100, 100));
        t.ajouterForme(new Cercle(50));

        t.afficherSurfaceTotale();
    }



Exercice 4: Transtypage (Héritage, Niveau 1)

4.1 Debugging

La méthode main du programme suivant contient deux erreurs liées à la notion de typage fort. Indiquez pour chaque erreur:

  1. quelle est l'erreur de typage commise
  2. comment corriger cette erreur

Corrigez le fichier Typage.java.

 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
class Typage {
    public static void main(String[] args) {
        A[] a = new A[5];                       
        a[0] = new B(8);                        
        a[1] = new B(8);                        
        a[2] = new C(4, 2);                     
        a[3] = new C(4, 2);                     
        a[4] = new D();                         
        int somme = 0;                          

        for (int i = 0; i < a.length; i++) {    
            if (a[i] instanceof D) {            
                System.out.println(((D) a[i]).d);   
            } else {                                
                if (a[i] instanceof B) {            
                    somme = somme + ma(a[i]);       
                    if (a[i] instanceof C) {        
                        C c = a[i];                 
                        c.mc();
                    }
                }
            }                                       
            System.out.println(somme);              
        }
    }

    static int ma(B x) {
        return x.b;
    }
}

class A {
    public A() {};
}

class B extends A {
    protected int b;

    public B(int b) {
        super();
        this.b = b;
    }
}

class C extends B {
    protected int c;

    public C(int b, int c) {
        super(b);
        c = b;
    }

    public void mc() {
        System.out.println("Bzz..");
    }
}

class D extends A {
    protected int d = 20;

    public D() {
        super();
    }
}

4.2 Polymorphisme d'inclusion: un peu de pratique

Le programme ABCDEF ci-dessous implémente la hiérarchie de 6 classes.

 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 A {
    public A() { }
}

class B extends A {
    public B() {
        super();
    }
}

class C extends B {
    public C() {
        super();
    }
}

class D extends A {
    protected int d = 1;

    public D(int x) {
        super();
        d = x;
    }

    public D() {
    }
}

class E extends D {
    public E() {
        super();
    }
}

class F extends D {
    public F() {
        super();
    }
}

class ABCDEF {
    public static void main(String[] args) {
        // Indiquez si les affectations suivantes sont correctes:
        A a = new A();
        B b = new B();
        C c = new C();
        D d = new D();
        E e = new E();
        F f = new F();

        a = b;
        b = a;
        a = (A) b;
        a = null;
        null = a;
        a = d;
        b = d;
        a = e;
        d = e;

        // Remplissage d'un tableau:
        A[] as = new A[10];
        as[0] = new A();
        as[1] = new B();
        as[2] = new D(2);
        as[3] = new E();
        as[4] = new C();
        as[5] = new D(4);
        as[6] = new B();
        as[7] = new F();
        as[8] = new C();
        as[9] = new F();

        // A vous d'ajouter le code de ces deux méthodes:
        rechercher(as);
        additionner(as);
    }

    private static void rechercher(A[] as) {
        // A remplir
    }

    private static void additionner(A[] as) {
        // A remplir
    }
}

Affectations entre classes

Dans la méthode main du programme ABCDEF, pour chaque affectation à des variables représentant des objets des classes A, B, C, D, E et F, indiquez si elle est correcte. Si ce n'est pas le cas, expliquez pourquoi.

La méthode rechercher

Dans la deuxième partie de la méthode main, le tableau as (de type A[]) est rempli d'objets des classes A, B, C, D, E et F. Ecrivez le code de la méthode rechercher pour qu'elle affiche le nombre d'objets de type B qui se trouvent dans le tableau. Exemple d'exécution:

  Il y a 4 instances de la classe B 

La méthode additionner

Ecrivez la méthode additionner pour qu'elle affiche la somme des variables d'instance d des objets qui se trouvent dans le tableau as. Exemple d'exécution:

  Somme des variables d : 9


Exercice 5: abstract et final (abstract, final, Niveau 1)

Le programme AbstractFinal ci-dessous implémente une petite hiérarchie de 4 classes (A, B, C et D). Il y a 3 erreurs dans la méthode main et 1 erreur dans la classe D. Toutes les erreurs sont dûes à une utilisation erronée des modificateurs abstract et final. Expliquez ces erreurs.

 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
class AbstractFinal {
    public static void main(String[] args) {
        
        A[] tab = new A[3];
        
        tab[0] = new A();
        tab[1] = new B();
        tab[2] = new C();
        
        tab[1].b = 2;
        ((C)tab[2]).c = 3;
    }
}

abstract class A {
    int a;
}

class B extends A {
    int b;
}

class C extends A {
    final double c = 1;
}

abstract class D extends A {
    double d;

    int operation(int a) {
        return (a * 2);
    }

    abstract int calcul(int b) {
        
    }

    abstract int machin();
}



Exercice 6: Affichage et comparaison d'objets (toString, equals, Niveau 1)

Programmer la hiérarchie de classes "Rectangle coloré héritant de Rectangle" (vue en cours) en obéissant aux contraintes suivantes :



Exercice 7: Tour de cartes (polymorphisme, Niveau 2)

Vous vous intéressez dans cet exercice à décrire les données d'un jeu simulant des combats de magiciens.

Dans ce jeu, il existe trois types de cartes : les terrains, les créatures et les sortilèges.

De plus, chaque carte, indépendamment de son type, possède un coût. Celui d'un terrain est 0.

Dans un programme Magic.java, proposez (et implémentez) une hiérarchie de classes permettant de représenter des cartes de différents types.

Chaque classe aura un constructeur permettant de spécifier la/les valeurs de ses attributs. De plus, chaque constructeur devra afficher le type de la carte.

Le programme doit utiliser la conception orientée objet et ne doit pas comporter de duplication de code.

Ajoutez ensuite aux cartes une méthode afficher() qui, pour toute carte, affiche son coût et la valeur de ses arguments spécifiques.

Créez de plus une classe Jeu pour représenter un jeu de cartes, c'est-à-dire une collection de telles cartes.
Cette classe devra avoir une méthode piocher permettant d'ajouter une carte au jeu. On supposera qu'un jeu comporte au plus 10 cartes. Le jeu comportera également une méthode joue permettant de jouer une carte. Pour simplifier, on jouera les cartes dans l'ordre où elles sont stockées dans le jeu, et on mettra la carte jouée à null dans le jeu de cartes.

Pour finir, dans la méthode main, constituez un jeu contenant divers types de cartes et faites afficher le jeu grâce à une méthode afficher propre à cette classe.

Par exemple la méthode main pourrait ressembler à quelque chose comme cela  :

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Magic {
    public static void main(String[] args) {
        Jeu maMain = new Jeu(10);

        maMain.piocher(new Terrain('b'));
        maMain.piocher(new Creature(6, "Golem", 4, 6));
        maMain.piocher(new Sortilege(1, "Croissance Gigantesque", 
                "La créature ciblée gagne +3/+3 jusqu'à la fin du tour"));

        System.out.println("Là, j'ai en stock :");
        maMain.afficher();
        maMain.joue();
    }
}

qui produirait quelque chose comme :

On change de main
Un nouveau terrain.
Une nouvelle créature.
Un sortilège de plus.
Là, j'ai en stock :
Un terrain bleu
Une créature Golem 4/6
Un sortilège Croissance Gigantesque
Je joue une carte...
La carte jouée est :
Un terrain bleu


Exercice 8: Analyse de programme (polymorphisme, clone, Niveau 1)

Un programmeur amateur produit le code suivant pour tester ses connaissances en POO :

  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
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
class Polymorph
{ public static void main(String[] args)
      
        {
            Forme[] tabFormes =
                {
                    new Cercle("rouge"),
                    new Triangle("jaune")
                };
            
            Collect formes = new Collect(10);

            // Une collection de formes
            // contenant une copie des objets definis
            // dans le tableau tabFormes
            for (int i = 0; i < tabFormes.length; ++i)
                formes.add(new Forme(tabFormes[i]));
            formes.dessine();
        }
}
                          


class Forme
{
    private String couleur;

    public Forme(String uneCouleur)
        {
            couleur = uneCouleur;
        }
    public Forme(Forme other)
        {
        this.couleur = other.couleur;
        }
    public  void dessine(){
        System.out.println("Une forme " + couleur);
    }
}



class Triangle extends Forme
{
    public Triangle(String uneCouleur)
        {
            super(uneCouleur);
        }
    
    public Triangle(Triangle autreTriangle)
        {
            super(autreTriangle);
        }
    
    public void dessine()
        {
            super.dessine();
            System.out.println("toute pointue");
        }
}

class Cercle extends Forme
{

    public Cercle(String uneCouleur)
        {
            super(uneCouleur);
        }
    public Cercle(Cercle autreCercle)
        {
            super(autreCercle);
        }
    public void dessine()
        {
            super.dessine();
            System.out.println("toute ronde");
        }
}


class Collect
{
    private Forme collect[];
    
    private int index;

    public Collect(int indexMax)
        {
            collect = new Forme[indexMax];
            index = -1;
        }
    
    public void add(Forme a)
        {
            if (index < collect.length - 1)
            {
                ++ index;
                collect[index] = a;
            }
            
        }
    public void dessine()
        {
            for (int i = 0; i <= index; ++i)
                collect[i].dessine();
        }
}

Il s’attend à ce que son programme produise l’affichage suivant :

Une forme rouge
toute ronde
Une forme jaune
toute pointue
Questions:

  1. Expliquez pourquoi ce programme principal ne fait pas réellement ce qu’il veut.
  2. Qu’affiche-t-il ?
  3. Corrigez ce programme de sorte à ce qu’il fasse ce que le programmeur voulait à l’origine. Vous
    donnerez le code Java de tout élément ajouté en précisant à quelle classe il appartient et
    indiquerez les éventuelles modifications à apporter à la méthode main.

Les contraintes à respecter sont les suivantes :



Exercice 9: Tutoriel I (préparation Mini-projet 2) (Fondamentaux de l'orienté-objet, Niveau 2)

Cette semaine, au vu des concepts présentés en cours et dans le MOOC, il est prévu que vous fassiez la partie I du tutoriel fourni pour réaliser le mini-projet 2 [Lien] (pages 1 à 17) . Please note that an automatically generated English translation [Link] , is also provided.


Exercice 10: 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 4 («Polymorphisme»).


Dernière mise à jour: 17/11/2023  (Revision: 1.2)