Encapsulation et composition en pratique
Champs privés, getters calculés, composition des droits.
Objectifs
À la fin de cette leçon, vous saurez :
- protéger l'état interne d'une classe ;
- exposer une interface intentionnelle plutôt que des champs nus ;
- refactorer un héritage fragile en composition.
L'état nu : le problème
const panier = { articles: [], total: 0 };
panier.articles.push("x");
panier.total = -50; // personne ne réagit !
panier.total = "gratuit"; // toujours moins...
Tout le programme peut corrompre l'état : chaque invariant (« total ≥ 0 », « total cohérent avec les articles ») dépend de la discipline de tous les développeurs, partout, pour toujours. Ça ne tient pas.
Encapsuler : état privé + méthodes publiques
class Panier {
#articles = []; // # = privé au langage (ES2022)
ajouter(article) {
this.#articles.push(article);
}
get nombre() {
return this.#articles.length;
}
vider() {
this.#articles = [];
}
}
const p = new Panier();
p.ajouter("pain");
p.#articles = "corrompu"; // SyntaxError : accès interdit
Le # rend le champ inaccessible depuis l'extérieur : la seule porte d'entrée est l'API que VOUS avez définie. Les invariants deviennent inviolables par construction — même principe que les contraintes SQL du chapitre base : rendre l'erreur impossible plutôt qu'interdite.
Exposer des comportements, pas des données
get total() { // getter calculé : jamais stocké deux fois
return this.#articles.length * PRIX_MOYEN;
}
Question directrice pour chaque membre : « est-ce un détail interne ou un service rendu ? ». Un détail → #. Un service → méthode/getter public. Une classe bien encapsulée se lit comme un menu de services, pas comme une vitrine de données.
Refactorer l'héritage fragile
// AVANT : héritage qui force tout
class UtilisateurAdmin extends Utilisateur {
constructor(nom) {
super(nom); // impose TOUT le comportement parent
}
}
// APRÈS : composition ciblée
class Droits {
peut(action) { return this.liste.has(action); }
}
class Utilisateur {
constructor(nom, droits = new Droits()) {
this.nom = nom;
this.droits = droits; // injecté, remplaçable
}
}
L'utilisateur n'hérite plus de « pouvoir » : il EN POSSÈDE, sous forme d'objet interchangeable. Admin, modérateur, invité deviennent des configurations de Droits, non des classes filles. C'est la composition du chapitre 27 appliquée aux permissions — motif central des systèmes à rôles comme Devmind (STUDENT/ADMIN).
Exercice
- Encapsulez votre classe Tache : titre non modifiable après création (#), basculer() seul moyen de changer fait.
- Ajoutez un getter
libellecalculé. - Transformez le rôle en classe Droits composée dans Utilisateur.
- Quel bénéfice concret tirent vos tests de l'encapsulation ?
Résumé
#champ= privé au langage ; l'état ne se traverse que par l'API.- Getters calculés évitent la duplication d'état.
- Composition remplace l'héritage quand la relation n'est pas « est-un ».
Correction disponibleCherchez d’abord par vous-même.Voir la correction
Correction
Réponses détaillées
Question 4. Les tests utilisent uniquement l'API publique (ajouter, basculer...) : ils restent valides si l'implémentation interne change (tableau → Map...). Sans encapsulation, tester revient à épier des champs internes — tests cassés à chaque refactoring.