À propos de ce cours
Chapitre 16 — La programmation orientée objet · 16.4 L'encapsulation : les champs et méthodes privés (#)
• expliquer le principe d'encapsulation ;
• déclarer des champs et des méthodes privés avec
#; • exposer une interface publique minimale (méthodes et getters) ;
• tester l'appartenance à une classe avec
#champ in objet. 1. Le problème : tout est modifiable
class CompteBancaire {
constructor(titulaire, solde) {
this.titulaire = titulaire;
this.solde = solde;
}
retirer(montant) {
if (montant > this.solde) throw new Error("Solde insuffisant");
this.solde -= montant;
}
}
const compte = new CompteBancaire("Amina", 100);
compte.solde = -5000; // rien n'empêche de contourner retirer()
compte.solde = "beaucoup"; // ni de mettre n'importe quoi
console.log(compte);
Résultat dans la console
CompteBancaire { titulaire: 'Amina', solde: 'beaucoup' }
La méthode retirer vérifie le solde… mais rien n'oblige à l'utiliser. N'importe quelle partie du programme peut mettre l'objet dans un état absurde.
2. L'encapsulation
Encapsuler, c'est cacher l'intérieur d'un objet et n'offrir à l'extérieur qu'une interface publique: les méthodes et getters prévus pour cela. L'objet garantit ainsi lui-même que ses données restent cohérentes. C'est comme une machine à café : on appuie sur les boutons, on n'accède pas à la chaudière.

Figure 1 — L'extérieur ne passe que par l'interface publique.
3. Les champs et méthodes privés : #
class CompteBancaire {
// Champs PRIVÉS : déclarés avec #, accessibles uniquement dans la classe
#solde = 0;
#historique = [];
constructor(titulaire, soldeInitial = 0) {
this.titulaire = titulaire; // public
this.deposer(soldeInitial);
}
// Lecture seule depuis l'extérieur : un getter sans setter
get solde() {
return this.#solde;
}
deposer(montant) {
this.#verifierMontant(montant);
this.#solde += montant;
this.#noter("dépôt", montant);
}
retirer(montant) {
this.#verifierMontant(montant);
if (montant > this.#solde) throw new Error("Solde insuffisant");
this.#solde -= montant;
this.#noter("retrait", montant);
}
releve() {
return this.#historique.map((op) => `${op.type} ${op.montant} DT`).join(", ");
}
// Méthodes PRIVÉES : détails internes
#verifierMontant(montant) {
if (typeof montant !== "number" || montant < 0) {
throw new TypeError("Montant invalide");
}
}
#noter(type, montant) {
this.#historique.push({ type, montant });
}
static estCompte(objet) {
return #solde in objet; // test d'appartenance à la classe
}
}
const compte = new CompteBancaire("Amina", 100);
compte.retirer(30);
console.log("Solde :", compte.solde);
console.log("Relevé :", compte.releve());
compte.solde = -5000; // pas de setter : ignoré (erreur en mode strict, voir ci-dessous)
console.log("Solde après tentative :", compte.solde);
console.log("Clés visibles :", Object.keys(compte));
console.log("JSON :", JSON.stringify(compte));
console.log("estCompte ?", CompteBancaire.estCompte(compte), CompteBancaire.estCompte({ solde: 1 }));
Résultat dans la console
Solde : 70
Relevé : dépôt 100 DT, retrait 30 DT
Solde après tentative : 70
Clés visibles : [ 'titulaire' ]
JSON : {"titulaire":"Amina"}
estCompte ? true false
- Un champ privé se déclare en haut de la classe avec
#:#solde = 0;. Le#fait partie du nom. - Il n'est accessible que dans le corps de la classe: il n'apparaît ni dans
Object.keys, ni dansJSON.stringify. - Un getter sans setter (
get solde()) offre une lecture seule. - Les méthodes privées (
#verifierMontant) cachent les détails internes.
Accéder à un champ privé depuis l'extérieur n'est même pas possible à écrire : c'est une erreur de syntaxe, détectée avant l'exécution.
class Coffre {
#code = 1234;
}
const c = new Coffre();
console.log(c.#code);
Dans le terminal : node l4_acces.mjs (extrait)
SyntaxError: Private field '#code' must be declared in an enclosing class
Dans un module (toujours en mode strict), écrire dans une propriété qui n'a qu'un getter déclenche une erreur au lieu d'être ignoré :
class Compte {
#solde = 100;
get solde() { return this.#solde; }
}
const c = new Compte();
try {
c.solde = 0; // un module est toujours en mode strict
} catch (e) {
console.log(e.constructor.name + " :", e.message);
}
Résultat dans la console
TypeError : Cannot set property solde of #<Compte> which has only a getter
Avant l'arrivée de
# (2022), on utilisait la convention du tiret bas : this._solde signifie « ne touchez pas ».
Ce n'est qu'une convention : rien ne l'empêche réellement. Vous la rencontrerez dans beaucoup de code existant.• Encapsulation : cacher les données, exposer une interface publique minimale.
•
#champ et #methode() sont privés et inaccessibles hors de la classe (erreur de syntaxe).• Getter sans setter = lecture seule ; les modifications passent par des méthodes qui vérifient.
•
_champ n'est qu'une convention ; #champ est une vraie protection.🛠 Activité 4 — Un compte bancaire inviolable
Mission : Réécrivez CompteBancaire pour qu'il soit impossible de le mettre dans un état incohérent.
Questions pour avancer (répondez dans votre compte rendu, puis appliquez dans votre projet) :
- Quelles données doivent être privées ? Lesquelles peuvent rester publiques ? Justifiez chaque choix.
- Ajoutez un plafond de retrait de 500 DT par jour, stocké dans un champ privé. Comment l'extérieur peut-il connaître le montant encore disponible, sans pouvoir le modifier ?
- Ajoutez
virer(montant, compteDestination). Dans cette méthode, avez-vous le droit d'écrirecompteDestination.#solde += montant? Testez. Que nous apprend le résultat sur la portée de#? - Écrivez
releve()qui renvoie l'historique. Si vous renvoyez directement le tableau privé#historique, que peut faire l'appelant avec ? Comment l'en empêcher ? - Faites
console.log(compte)dans Node.js puis dans la console du navigateur. Les champs privés apparaissent-ils ? Est-ce une faille de sécurité ? - Votre application doit sauvegarder les comptes en JSON.
JSON.stringifyignore les champs privés : comment faire ? (indice : une méthodetoJSON()et une fabrique statiquedepuisJSON())
Je vérifie / je rends :
- ☐ La classe complète.
- ☐ Une liste de cinq « attaques » que votre classe bloque, avec le message d'erreur obtenu.
← 16.3 Getters, setters et membres statiques16.5 L'héritage des classes (extends et super) →