Version sans JavaScript, en lecture seule : les ateliers, les quiz et l’exécution du code n’y sont pas. Version complète · Catalogue · Se connecter
Support de cours · chapitre 3
La lutte des classes
Les quatre relations entre classes (association, agrégation, composition, héritage) et le critère qui permet de les distinguer, la durée de vie des objets.
Ce chapitre prolonge la séquence 1. Il présente les quatre relations entre classes, leur notation UML, et une erreur de lecture fréquente qui les fait paraître identiques dans le code.
Association : un simple lien
Dans une association, deux objets se connaissent, mais aucun ne dépend de l'autre pour exister.
class Canard {
private String nom;
Canard(String nom) { this.nom = nom; }
}
class Dresseur {
private Canard canard;
Dresseur(Canard c) { this.canard = c; } // on range la référence
}
En UML, l'association se note par un trait simple. Le dresseur entraîne le canard, mais si l'on supprime l'un des deux, l'autre continue d'exister.
Ici, ranger la référence telle quelle est correct, puisque c'est le sens même de la relation : le dresseur doit s'adresser à ce canard précis, et non à une copie.
Variante bidirectionnelle. Il arrive que le canard doive aussi connaître son dresseur. C'est possible, mais cela ajoute du couplage : les deux classes ne peuvent plus être lues l'une sans l'autre. On ne le fait donc que si le besoin l'impose.
Agrégation : « possède », faiblement
Dans une agrégation, un objet en regroupe d'autres, qui peuvent continuer d'exister sans lui.
class Troupe {
private List<Canard> canards = new ArrayList<>();
void ajouter(Canard c) { canards.add(c); }
}
En UML, l'agrégation se note par un losange blanc. La troupe est une collection de références : si on la dissout, les canards continuent d'exister.
Composition : « fait partie de »
Dans une composition, la partie est créée et détruite avec le tout.
class Aile {
private int puissance;
Aile(int puissance) { this.puissance = puissance; }
}
class Canard {
private Aile aileGauche = new Aile(10);
private Aile aileDroite = new Aile(10);
}
En UML, la composition se note par un losange noir. Les ailes n'existent pas sans le canard : c'est lui qui les crée, et aucun autre objet n'en garde de référence.
C'est ce dernier point qui caractérise la composition, et c'est aussi celui qu'on oublie le plus souvent. Un constructeur qui reçoit l'aile de l'extérieur et la range telle quelle ne réalise pas une composition, car l'appelant garde une référence vers l'objet.
class Canard {
private final Aile gauche;
Canard(Aile gauche) {
this.gauche = gauche; // ← l'appelant tient encore l'aile
}
}
Le mot-clé final n'y change rien, car il rend la référence constante mais pas l'objet. La démonstration se trouve au chapitre 2 ; il est utile de l'exécuter au moins une fois.
Pour obtenir une vraie composition, il faut faire une copie :
Canard(Aile modele) {
this.gauche = new Aile(modele.puissance()); // un exemplaire à soi
}
Héritage : « est un »
class Canard {
void cancaner() { System.out.println("Coin coin !"); }
}
class CanardColvert extends Canard {
@Override void cancaner() { System.out.println("Coin coin (colvert) !"); }
}
class CanardEnPlastique extends Canard {
@Override void cancaner() { System.out.println("Couic couic !"); }
}
En UML, l'héritage se note par une flèche à pointe creuse dirigée vers la super-classe. La sous-classe hérite du comportement de la super-classe et peut le spécialiser. C'est la relation la plus puissante, mais aussi la plus coûteuse ; le chapitre 4 lui est entièrement consacré.
Récapitulatif UML
classDiagram
direction LR
class Dresseur
class Troupe
class Canard
class Aile
class CanardColvert
class CanardEnPlastique
Dresseur --> Canard : association
Troupe o-- Canard : agrégation
Canard *-- Aile : composition
Canard <|-- CanardColvert : héritage
Canard <|-- CanardEnPlastique : héritageLes classes fourre-tout
Une classe à laquelle on ajoute sans cesse des méthodes finit par tout faire, et ses relations avec les autres classes deviennent difficiles à lire.
class Canard {
private String nom;
private int vitesse;
private String couleur;
void voler() { }
void nager() { }
void chanter() { }
void sauvegarderEnXML() { } // ← rien à voir
void imprimerFacture() { } // ← rien à voir
void gererStock() { } // ← rien à voir
}
On appelle ce type de classe un God Object. Il est traité au chapitre 8 ; retenez pour l'instant qu'on corrige ce défaut par la composition :
class Canard {
private String nom;
private VolStrategy vol;
private ChantStrategy chant;
}
Chaque responsabilité est placée dans sa propre classe, et le canard combine ces objets au lieu de contenir lui-même tout le code.
Question d'épreuve
Qu'est-ce qui distingue une composition d'une agrégation dans le code ?
La composition crée elle-même la partie et n'en laisse aucune référence à l'extérieur ; l'agrégation reçoit des éléments qui continuent d'exister sans elle. Dans le constructeur, on trouve donc un new dans le premier cas et une simple affectation dans le second.