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 7
Encapsulation et invariants
Rendre un attribut privé ne suffit pas à protéger un objet. L'encapsulation sert à garantir des invariants, c'est-à-dire des propriétés qui doivent rester vraies.
Ce chapitre prolonge la séquence 4.
Ce que protège l'encapsulation
L'encapsulation a pour but de garantir des propriétés qui doivent rester vraies pendant toute la vie de l'objet : un prix n'est jamais négatif, un nom n'est jamais vide, une position reste dans le bassin. Ces propriétés s'appellent des invariants, et l'encapsulation permet de les faire respecter.
public class CanardEnPlastique {
private String nom;
private int prix; // en centimes
private double x, y; // position dans le bassin
public CanardEnPlastique(String nom, int prix) {
setNom(nom); // le constructeur passe par les mêmes garde-fous
setPrix(prix);
}
public String getNom() { return nom; }
public int getPrix() { return prix; }
}
Les modifications passent par des méthodes qui vérifient les valeurs :
public void setNom(String nom) {
if (nom == null || nom.trim().isEmpty())
throw new IllegalArgumentException("le nom est obligatoire");
this.nom = nom;
}
public void setPrix(int prix) {
if (prix < 0)
throw new IllegalArgumentException("prix >= 0");
this.prix = prix;
}
Getter et collection interne
public class Troupe {
private final List<Canard> canards = new ArrayList<>();
public List<Canard> getCanards() { return canards; } // ← la fuite
}
L'attribut est private et final, et pourtant n'importe quel code peut écrire troupe.getCanards().clear(). L'invariant « une troupe a entre trois et huit canards » n'est alors plus garanti.
Le même problème se pose pour tout objet mutable renvoyé tel quel. C'est le piège du final vu au chapitre 2 : la référence ne peut plus changer, mais l'objet référencé, si.
Les niveaux de visibilité
| Portée | Qui voit |
|---|---|
private | la classe seule |
| (rien) | le paquetage |
protected | le paquetage et les sous-classes |
public | tout le monde |
Rendre une méthode public engage pour la suite : une fois publiée, elle est utilisée par du code que vous ne contrôlez pas, et on ne peut plus la retirer. Il vaut donc mieux commencer par private et élargir la visibilité si nécessaire.
Les objets immuables
L'encapsulation la plus simple consiste à rendre l'objet immuable : s'il ne change jamais, ses invariants n'ont pas à être protégés après la construction.
public final class Accessoire {
private final String type; // "chapeau", "cape", "bouée"
private final String couleur;
public Accessoire(String type, String couleur) {
this.type = type;
this.couleur = couleur;
}
public String description() { return type + " " + couleur; }
}
Le final sur la classe empêche de créer une sous-classe qui ajouterait un champ mutable. Le final sur les champs les fixe à la construction, et la classe n'a aucun mutateur. Un tel objet peut être partagé sans précaution, placé dans un Set sans risque, et lu par plusieurs fils d'exécution sans verrou.
C'est souvent le bon choix pour les valeurs (une couleur, un montant, une coordonnée), par opposition aux entités, qui ont une identité et évoluent dans le temps.
Les classes abstraites
Une classe abstraite indique ce que toutes ses sous-classes devront savoir faire, sans préciser comment.
public abstract class CanardAbstrait {
private final String nom;
public CanardAbstrait(String nom) { this.nom = nom; }
public String getNom() { return nom; }
public abstract void cancaner(); // promesse, pas implémentation
public void translater(double dx, double dy) { /* code commun */ }
}
On ne peut pas écrire new CanardAbstrait("..."), puisque cancaner() n'a pas d'implémentation. Chaque sous-classe fournit la sienne.
Classe abstraite ou interface ? Une classe abstraite apporte un état et du code commun, et une classe ne peut hériter que d'une seule. Une interface décrit seulement un rôle, et une classe peut en implémenter autant qu'elle veut. En cas d'hésitation, l'interface est presque toujours le bon choix (voir le chapitre 5).
Erreurs fréquentes
Question d'épreuve
Un champ est
private finalet la classe n'a aucun setter. L'objet est-il protégé ?
Pas nécessairement. final empêche de changer la référence, mais pas de modifier l'objet référencé : si le champ contient un objet mutable reçu de l'extérieur, ou renvoyé par un getter, n'importe qui peut le modifier. Pour le protéger, il faut faire une copie à l'entrée et à la sortie, ou utiliser un type immuable.