Les "chenilles" : Episode II

Objectifs : Il s'agit de se familiariser avec les notions d'héritage, de réutilisation et d'abstraction et de polymorphisme et à leur réalisation dans le langage JAVA.

Dans ce TP vous allez utilisez les nouvelles notions vues en cours: les classes abstraites et les interfaces.

Dans un TP précédent (TP 7) vous avez développé une application permettant l'animation de "Chenilles" se déplaçant à l'intérieur d'une fenêtre. Pour cela vous avez réutilisé du code issu de l'application d'animation de visages vue lors du premier TP (TP1). En particulier vous avez du modifier le code de la classe Dessin afin de pouvoir afficher non plus des objets de classe VisageRond, mais des objets de classe Chenille.

Maintenant que vous avez découvert le polymorphisme et les concepts de classes abstraites et d'interfaces, vous disposez des connaissances techniques qui vous permettent d'envisager une application beaucoup plus ambitueuse animant simultanément des objets de classe VisageRond et Chenille et pourquoi pas d'autres choses dont vous n'avez pas encore idée.

Exercice 1 : animer des visages avec les chenilles.

On souhaite modifier l'application d'animation des Chenilles, afin de pouvoir animer simultanément dans une même zone de dessin à la fois des objets Chenille et des objets VisageRond. La figure ci-dessous montre le résultat attendu.

Animer des chenilles et des visages
  1. Si ce n'est déjà fait, récupérez la correction du TP Chenille

  2. Décompressez l'archive que vous avez téléchargée puis ouvrez le projet correspondant sous VSCode et vérifiez que l'application AppliNChenille fonctionne correctement.

  3. Intégrez à ce projet la classe VisageRond.java vue lors des TP1 et TP2 et modifiez le code de manière à pouvoir animer simulaténement des visages et chenilles.

    Quelles modifications avez-vous du apporter aux classes VisageRond, Chenille et Dessin pour obtenir ce résultat ?

    Pour rappel, la diagramme UML des classes de l'application Chenilles

Essayez de trouvez une solution par vous-même. Cependant, si vous n'avez pas d'idées sur la manière de procéder, le bouton ci dessous vous fournira quelques indications.

Pour la zone de dessin, ce qui importe, c'est que les objets qu'elle contient puissent se dessiner (plus précisément possèdent un méthode public void dessiner(Graphics g)) et se deplacer (méthode public void deplacer(). C'est le cas des classes Chenille et VisageRond.

L'idée est donc de

  1. Définir une interface IAnimable.

  2. Indiquer que les classes Chenille et VisageRond réalisent (implémentent) cette interface.

  3. Modifier la classe Dessin, pour que la liste d'objets qu'elle contient ne soit plus une liste de Chenille mais de IAnimable.


Exercice 2 : afficher des étoiles fixes

On souhaite modifier l'application afin de pouvoir afficher, en plus des Chenilles et Visages animées, des étoiles qui elles demeurent fixes.

Pour construire et dessiner une étoile à 5 branches vous pourrez procéder comme indiqué sur la figure suivante.

Figure 1: Comment dessiner une étoile à 5 branches

Le code java correspondant à ces différentes étapes vous est donné ci-dessous

import java.awt.BasicStroke;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;

int nbSommets = 5;

// Etape 1
// calcul des sommets de l'étoile
float deltaAngle = 360f / nbSommets;
float angle = -90;
Point2D.Float[] sommets = new Point2D.Float[nbSommets];
for (int i = 0; i < nbSommets; i++) {
    sommets[i] = new Point2D.Float((float) Math.cos(Math.toRadians(angle)) * r,
    (float) Math.sin(Math.toRadians(angle)) * r);
    angle += deltaAngle;
}

// Etape 2
// construction du chemin reliant les points
Path2D contour = new Path2D.Float();
contour.moveTo(sommets[0].getX(), sommets[0].getY());
contour.lineTo(sommets[2].getX(), sommets[2].getY());
contour.lineTo(sommets[4].getX(), sommets[4].getY());
contour.lineTo(sommets[1].getX(), sommets[1].getY());
contour.lineTo(sommets[3].getX(), sommets[3].getY());
contour.closePath();

// dessin à l'aide d'un objet Graphics g
Graphics2D g2 = (Graphics2D) g.create();   // on crée une copie de g

// Etape 3
// dessin du contour
g2.setColor(couleurTrait);
g2.setStroke(new BasicStroke(8.0f));
g2.translate(x, y);  // x et y le centre du cercle définissant l'étoile
g2.draw(contour);

// Etape 4
// Remplissage de la forme
g2.setPaint(couleurRemplissage);
g2.fill(contour);

Question 1: Créez une nouvelle classe Etoile qui permet de dessiner un étoile à 5 branches. Pour écrire ce code, vous vous devez de répondre aux questions suivantes:

  1. Quels sont les attributs qui caractérisent une étoile

  2. Quel est le constructeur pour cette classe ?

  3. Quelles méthodes ?

  4. Comment faire pour que les objets instance de cette classe puissent être ajoutés à la zone de dessin ?

Essayez de trouvez une solution par vous-même. Cependant, si vous n'avez pas d'idées sur la manière de procéder, le bouton ci dessous vous fournira quelques indications.

Pour pouvoir être affichée dans le dessin une étoile doit être un objet dessinable (objet possèdant une méthode public void dessiner(Graphics g))). Par contre les étoiles sont fixes, elles n'ont pas de méthode public void deplacer().

Pour pouvoir distinguer les objets pouvant se dessiner des objets pouvant se dessiner et aussi se déplacer on peut créer une nouvelle interface IDessinable contenant la méthode public void dessiner(Graphics g) et définir le type IAnimable comme étant un sous type de IDessinable. On peut ainsi refactorer le code de la classe dessin pour que :

  • le type du paramètre de la méthode ajouterObjet soit non plus IAnimable mais IDessinable,
  • la liste des objets du dessin soit typée avec IDessinable au lieu de IAnimable,
  • la méthode déplaçant tous les objets ne fasse se déplacer que les objets dont le type (la classe) implémente l'interface IAnimable.

La classe Etoile doit donc implémenter l'interface IDessinable.

Les attributs d'un objet étoile sont:

  • les coordonnées x,y du centre de l'étoile, des entiers,
  • le rayon, un entier,
  • l'épaisseur du trait de contour, un flottant,
  • les couleurs du trait de contour et de remplissage,
  • l'objet Path2D qui défini le tracé du contour de l'étoile.

L'attribut contour est calculé dans le constructeur (Etapes 1 et 2). Il est utilisé dans la méthode dessiner dont le code correspond aux étapes 3 et 4.

Les paramètres du constructeur permettent de fixer les valeurs des attributs x,y, r, epaisseurTrait, couleurTrait et couleurRemplissage.

Question 2: Modifiez l'application AppliNChenilles pour afficher en plus des chenilles et des visages deux étoiles, une étoile jaune à bords rouges de rayon 100 pixels, et une étoile bleue à bords verts de rayon 50 pixels.

Figure 2: les chenilles, les visages et les étoiles.

Exercice 3 : afficher d'autres formes fixes: les polygones réguliers

On veut maintenant afficher un nouveau type de formes fixes: les polygones réguliers, polygones inscrits dans un cercle et aux cotés de longueur identique.

Pour un polygone régulier (par exemple un pentagone) seule l'étape 2 diffère, il suffit de prendre les sommets consécutifs

Figure3: Construction d'un polygone régulier (Pentagone)

Question 1: Définir une nouvelle classe de forme fixe, PoygoneRegulier. Pour écrire ce code, vous devez comme pour la classe Etoile répondre aux questions suivantes:

  1. Quels sont les attributs qui caractérisent un polygone régulier

  2. Quel est le constructeur pour cette classe ?

  3. Quelles méthodes ?

  4. Comment faire pour que les objets instance de cette classe puissent être ajoutés à la zone de dessin ?

Mais en plus vous devez vous posez les questions suivantes:

  1. Quel est le code commun entre les classes Etoile et PolygoneRegulier ?

  2. Comment éviter de dupliquer ce code et le partager efficacement ?

Essayez de trouvez une solution par vous-même. Cependant, si vous n'avez pas d'idées sur la manière de procéder, le bouton ci dessous vous fournira quelques indications.

PolygoneRegulier partage une bonne partie de son code avec Etoile:

  • les attributs,

  • la partie du constructeur correspondant au calcul des sommets (étape1),

  • et la méthode dessiner.

Tout ce code peut être factorisé dans une super-classe abstraite FormeCirculaireReguliere.

La classe PolygoneRegulier hérite de FormeCirculaireReguliereimplemente simplement un constructeur qui réalise l'initialisation du contour (étape 2).

La classe Etoile elle aussi, de manière similaire à PolygoneRegulier hérite de FormeCirculaireReguliere et réalise l'initialisation du contour (étape 2) dans son constructeur.

Question 2: Modifiez l'application AppliNChenilles pour en plus des objets précédents afficher un pentagone et un octogone que vous placerez où bon vous sied.

Exercice 4 : Généralisation des formes

La généralisation des concepts Etoile et PolygoneRegulier en FormeCirculaire a permis de factoriser le code commun à ces deux types de formes. Cette factoristaion peut être poussée plus loin pour offrir plus de généralité et faciliter des évolutions futures du logiciel en permettant d'intégrer facilement n'importe quel type de formes.

Question : définir deux nouvelles classes abstraites Forme et FormeCirculaire qui généralisent le concept de FormeCirculaireRégulière comme le montre la figure ci-dessous.

Exercice 5 : animer les formes

On souhaite maintenant étendre l'application de façon à ce que les formes (étoiles, polygones réguliers,...) puissent être au choix :

  • fixes comme précédemment,
  • animées.

Dans le cas où la forme est animée, plusieurs types de mouvements sont possibles (voir figure 3):

  • un déplacement analogue à celui des VisagesRond, c'est à dire que la forme se déplace dans une direction et rebondit sur les bords de la fenêtre quand elle les touche,
  • un déplacement circulaire, c'est à dire que la forme tourne autour d'un point, centre de rotation.
Figure 3: types de trajectoires des formes animées

Question 1: Comment procéderiez vous pour permettre la cohabitation de formes fixes et de formes animées, sachant que les formes animées doivent pouvoir être dotées de différents types de trajectoire ? Quelle modélisation proposeriez vous pour permettre cela tout en garantissant une extensibilité aisée de l'application (c'est à dire de pouvoir ultérieurement ajouter facilement de nouveaux types de formes et de trajectoires) ?

Pour synthétiser vos propositions, donnez un diagramme de classes UML représentant les différentes classes et interfaces de votre application et leurs relations.

Essayez de trouvez une solution par vous-même. Cependant, si vous n'avez pas d'idées sur la manière de procéder, le bouton ci dessous vous fournira quelques indications.

L'idée est de définir une classe FormeAnimee qui associe une Forme à un IAnimateur (type abstrait pour un animateur) et ensuite de créer différentes classes d'animateurs réalisant l'interface IAnimateur :

  • AnimateurCirculaire qui anime une forme se déplaçant sur un cercle,
  • AnimateurRebonde qui anime une forme se déplaçant selon une trajectoire rectiligne et rebondissant lorsqu'elle touche les bords du dessin,

Question 2: Modifiez votre application afin d'intéger les modifications précédentes.

Les parties précédentes avaient pour objectif de vous familiariser avec les notions de classes abstraites et d'interface en Java. Les parties qui suivent sont "facultatives", dans le sens où elles n'introduisent pas de nouvelles notions. Aussi ne paniquez pas si vous n'avez pas le temps de les faire, vous pourrez vous contenter de regarder les vidéos de solutions qui seront proposées (bientôt :-)).

Exercice 6 : un nouveau type d'animateur : AnimateurCap

On voudrait maintenant que les formes circulaires (étoiles, polygones réguliers) puissent être également dotées d'un mouvement analogue à celui des Chenilles : déplacement avec un variation aléatoire d'un cap sans jamais sortir de la zone de dessin.

Question 1: Comment procéderiez vous pour mettre en oeuvre ce nouveau type d'animateur ?

Pour synthétiser vos propositions, donnez un diagramme de classes UML représentant les différentes classes et interfaces pour les animateur et leurs relations.

Essayez de trouvez une solution par vous-même. Cependant, si vous n'avez pas d'idées sur la manière de procéder, le bouton ci dessous vous fournira quelques indications.

On s'aperçoit qu'un AnimateurCap va devoir de la même manière qu'un AnimateurRebond tester que la forme qu'il anime ne sort pas de la zone de dessin. Afin de partager ce code commun, l'idée est de définir une classe abstraite AnimateurAvecDess dans laquelle on fait remonter les methodes sortAGauche(), sortADroite(), sortEnHaut() et sortEnBas() et des définir les classes AnimateurRebond et AnimateurCap comme des sous classes de cette classe abstraite.

Question 2: Modifiez votre application afin d'intéger ce nouveau type d'animateur.

Exercice 7 : permettre différents comportements d'animation pour les Chenilles

On voudrait maintenant que les chenilles, de manière similaire aux formes, puissent être animées avec au choix : un déplacement aléatoire, un déplacement avec rebondissement sur les bords de la fenêtre ou un déplacement circulaire. La vidéo ci dessous montre le résultat attendu.

Question 1: Quelles ajout/modifications doivent être apportées à votre code pour offrir ces nouvelles fonctionnalités ? Modifiez votre diagramme de classes UML pour de manière synthétique la nouvelle organisation des classes et interface de votre application.

Essayez de trouvez une solution par vous-même. Cependant, si vous n'avez pas d'idées sur la manière de procéder, le bouton ci dessous vous fournira quelques indications.

Au niveau de Chenille il faut déléguer l'animation de la tête à un IAnimateur et pour cela faire rentrer les Anneaux dans une hiérarchie de type uniforme avec Forme en introduisant un nouveau type abstrait (interface) IForme.

Question 2: Modifiez votre code afin de prendre en compte cette nouvelle modélisation.