Avez-vous une question? (216) 97 656 803 hajjriadh@gmail.com
JavaScript

L'encapsulation : les champs et méthodes privés (#)

L'encapsulation : les champs et méthodes privés (#). Dans cette leçon du cours JavaScript, vous apprendrez à : expliquer le principe d'encapsulation ; déclarer des champs et des méthodes privés avec #...

0 chapitres 0 QCM Accès gratuit
Dans cet article

À propos de ce cours

L'encapsulation : les champs et méthodes privés (#). Dans cette leçon du cours JavaScript, vous apprendrez à : 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.

Chapitre 16 — La programmation orientée objet · 16.4 L'encapsulation : les champs et méthodes privés (#)

🎯 Objectifs de la leçon
• 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.

Interface publique et partie privée d'un objet
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 dans JSON.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
ℹ️ À retenir
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.
ℹ️ À retenir
• 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) :

  1. Quelles données doivent être privées ? Lesquelles peuvent rester publiques ? Justifiez chaque choix.
  2. 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 ?
  3. Ajoutez virer(montant, compteDestination). Dans cette méthode, avez-vous le droit d'écrire compteDestination.#solde += montant? Testez. Que nous apprend le résultat sur la portée de #?
  4. É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 ?
  5. 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é ?
  6. Votre application doit sauvegarder les comptes en JSON. JSON.stringify ignore les champs privés : comment faire ? (indice : une méthode toJSON() et une fabrique statique depuisJSON())

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) →