Table des matières

TD 3 - Héritage

A la fin du TP2, vous avez dû créer plusieurs classes en plus des classes Point2D et Circle: des classes pour représenter les triangles ainsi que les rectangles et les carrés alignés avec les axes. Appelons ces classes Triangle, AxesAlignedRectangle et AxesAlignedSquare respectivement.

Toutes ces classes contiennent des méthodes de calcul de périmètre et d’aire, et des méthodes de déplacement. Par simplicité, oublions ici la méthode permettant de savoir si un point est à l’intérieur de la forme.

Dans un tel cas de figure, c’est-à-dire

on utilise, en programmation orientée objet, le concept d’héritage.

Pour ceci, on définit une classe “mère” qui contiendra les méthodes communes, et les attributs communs s’il y en a. Dans le cas de notre exemple, on appelera cette classe Shape2D, et elle définira les méthodes perimeter, area, et translation.

Les classes Point2D, Circle, AxesAlignedRectangle, AxesAlignedSquare, et Triangle vont “hériter” de cette classe. Cela signifie qu’elles auront les méthodes et attributs de Shape2D. Elles peuvent néanmoins redéfinir ces méthodes. Par exemple, l’aire d’un cercle et l’aire d’un triangle ne se calcule pas de la même façon, et les méthodes area de Circle et Triangle auront donc des codes différents.

Les classes qui héritent de la classe mère peuvent aussi avoir des méthodes et des attributs supplémentaires qui leur sont propres. Par exemple, la classe Point2D pourra avoir une méthode distance, sans que les autres classes aient une telle méthode.

Les avantages de l’héritage sont :

Classe Shape2D

Notre “classe mère” sera la classe Shape2D donnée ci-dessous :

public class Shape2D {

    public double area() {
      return 0;
    }
    
    public double perimeter() {
        return 0;
    }
    
    public void translate(int dx, int dy) { }

    public void translate(int delta) {
        translate(delta, delta);
    }

    public void print() {
        System.out.println("Shape2D");
    }
}

Le code des méthodes area, perimeter, et de la première méthode translate ne peuvent pas encore être écrits, et le corps de ces méthodes sont vides.

En revanche, notez que la deuxième méthode translate(int delta) est déjà définie. Comme elle se contente d’appeller translate(int dx, int dy), nous n’aurons à définir que cette méthode translate(int dx, int dy) dans les classes qui héritent de Shape2D.

La classe Point2D devient :

public class Point2D extends Shape2D {
    private int x, y;
    
    public Point2D(int x, int y) {
        this.x = x;
        this.y = y;
    }
    
    public Point2D(Point2D p){
        this(p.x, p.y);
    }

    public int getX(){
        return this.x;
    }

    public int getY(){
        return this.y;
    }

    public void setX(int x){
        this.x = x;
    }

    public void setY(int y){
        this.y = y;
    }

    public void translate(int dx, int dy) {
        x = x + dx;
        y = y + dy;
    }

    public void print() {
        System.out.println( "Point2D (" + x + ", "  + y + ")");
    }

    public double distance(Point2D p){
        int d1 = p.x - x;
        int d2 = p.y - y;
        return Math.sqrt(d1*d1 + d2*d2);
   }

}

Premier test

Testez Shape2D et Point2D avec :

public class TestShape2D {
    public static void main(String[] args) {
        Point2D  p1 = new Point2D(1, 2);
        p1.print();
        p1.translate(5);
        p1.print();        
    }
}

Au travail

Implémentez les classes Circle, AxesAlignedRectangle, AxesAlignedSquare, et Triangle sur le même modèle que la classe Point2D. Il faudra bien sûr ici implémenter les méthodes area et perimeter en plus de print, ainsi que les constructeurs de chaque classe. Vous pouvez récupérer votre code de la semaine dernière et l’adapter.

N’oubliez pas de testez vos classes.

Polymorphisme

Avec le polymorphisme, dans le cas de notre exemple, une variable peut être déclarée de type Shape2D, et contenir un objet de n’importe quelle classe qui hérite de cette classe Shape2D. Par exemple, on peut faire :

        Shape2D s = new Point2D(1, 2);
        s.print();

        Point2D A = new Point2D(0, 0);
        Point2D B = new Point2D(5, 0);
        Point2D C = new Point2D(0, 5);
        s = new Triangle(A, B, C);
        s.print();        

La variable s correspond d’abord à un Shape2D puis à un triangle. Testez ce code.

Un tableau de Shape2D

Créez un tableau de Shape2D, et écrivez le code qui remplit ce tableau au hasard de différentes formes géométriques en utilisant les classes définies plus haut. C’est un exemple d’utilisation du polymorphisme, puisque les éléments du tableau sont de type Shape2D, et peuvent contenir des objets d’un des types qui héritent de Shape2D.

Affichez ensuite les éléments de ce tableau.

Shape2D devient grand-mère

La classe AxesAlignedSquare ressemble beaucoup à la classe AxesAlignedRectangle. Faites hériter AxesAlignedSquare de AxesAlignedRectangle plutôt que de Shape2D. Adaptez le code de la classe AxesAlignedSquare en conséquence, il peut devenir très court si vous vous y prenez bien.

Pour appeler le constructeur de la classe mère, on appelle la méthode super, qui désigne ce constructeur : si AxesAlignedSquare hérite de AxesAlignedRectangle, appeler super(...) dans une méthode de AxesAlignedSquare appelle le constructeur de AxesAlignedRectangle.

toString

En Java, toutes les classes héritent implicitement de la classe Object.

Cette classe Object contient entre autre la méthode toString. Cette méthode est appelée par println pour afficher une variable. La méthode toString définie dans Object retourne simplement la référence de la variable, mais en la redéfinissant dans nos classes, println va afficher les variables comme nous le souhaitons.

Par exemple, ajoutez le code suivant dans la classe Point2D :

    public String toString() {
        return "Point2D (" + x + ", "  + y + ")";
    }

La méthode toString doit retourner une chaine de caractères (String) contenant ce qui doit être affiché. On peut l’utiliser simplement, par exemple (dans TestShape2D) :

        System.out.println(p1);

On peut maintenant écrire par exemple :

        Point2D p2 = new Point2D(5, 0);
        System.out.println("Le premier point est " + p1 + " et le deuxieme " + p2);

au lieu de

        Point2D p2 = new Point2D(5, 0);
        System.out.print("Le premier point est ");
        p1.print();
        System.out.print(" et le deuxieme ");
        p2.print();

qui est moins flexible et moins élégant.

Ajoutez les méthodes toString aux autres classes.

Pour aller plus loin

Inclusion dans un cercle

On voudrait ajouter une méthode dans la classe Circle qui teste si la forme passée en argument est incluse entièrement dans le cercle. La difficulté vient du fait que le test change avec le type de la forme.

Une façon de faire serait d’écrire une méthode pour chaque classe possible :

        boolean inside(Point2D p);
        boolean inside(Triangle t);
        boolean inside(AxesAlignedRectangle r);
        boolean inside(AxesAlignedSquare s);
        boolean inside(Circle c);

Pour éviter de répéter autant cette méthode, nous allons ajouter une classe Polygone qui héritera de Shape2D et dont Triangle et AxesAlignedRectangle hériteront (rappelez-vous que AxesAlignedSquare hérite déjà de AxesAlignedRectangle).

Ajoutez dans cette classe Polygone une méthode vertices qui renvoie un tableau contenant les sommets du polygone. Implémentez cette méthode dans Triangle et AxesAlignedRectangle.

Vous pouvez maintenant écrire les méthodes

        boolean inside(Point2D p);
        boolean inside(Polygone p);
        boolean inside(Circle c);

dans la classe Circle. inside(Polygone p) utilisera vertices et inside(Point2D p).

SVG

Vous pouvez continuer la création de fichiers SVG comme vu à la fin du TD précédent. Ajoutez une méthode String svg() et une couleur sous la forme de 3 attributs r, g, b dans la classe Shape2D. Redéfinissez svg dans les classes qui héritent de Shape2D, et utilisez-les pour créer des fichiers svg qui contiennent des formes géométriques.