Polar Code 🎭

Command Palette

Search for a command to run...

08
Pièce N°08

Module 8 -- POO : Organiser le Code Comme une Cellule Dormante

Jusqu'ici, tu écrivais du code "de terrain". Des variables qui traînent, des fonctions qui agissent dans l'urgence. La POO (Programmation Orientée Objet), c'est une autre discipline : tu organises tout autour d'objets. Comme si tu créais des agents, chacun avec son dossier, ses méthodes, ses secrets.

Pourquoi passer à ça ?

  • Caché : Tu crées un "modèle" (une classe) et tu le clones partout sans laisser de traces.
  • Ordonné : Tout ce qui concerne un "Contact" est au même endroit.
  • Robuste : Modifier un détail sans faire s'écrouler l'ensemble.
  • Propre : Plus de variables orphelines qui flottent comme des témoins gênants.

En JavaScript, il y a toujours deux visages :

  1. L'ancien système (prototypes) : La mécanique réelle, sous le capot.
  2. La nouvelle syntaxe (classes ES6) : Une façade plus propre, mais qui utilise les mêmes engrenages.

On survole rapidement l'ancien, juste pour comprendre les traces. Puis on s'installe dans le moderne.


8.1 Le Système Prototype - Les Fondations Sous la Ville

Le Modèle Objet de JavaScript

// En JS, presque tout est un objet, même ce qui n'en a pas l'air.
let liste = [1, 2, 3];
console.log(typeof liste); // "object"

let fonction = function() {};
console.log(typeof fonction); // "function" (un objet spécial)

// Même les types primitifs ont des doublures objets
let phrase = "code rouge";
console.log(phrase.toUpperCase()); // "CODE ROUGE"
// Une string primitive qui a une méthode ? C'est le prototype qui agit.

Le Chaînage de Prototypes - Comment JS Trouve ce Qu'il Cherche

// Chaque objet a un lien vers son "prototype parent". Une chaîne invisible.
let source = {
  méthodeSecrète: function() {
    return "Message codé.";
  }
};

let relais = {
  signal: function() {
    return "Signal émis.";
  }
};

// On relie relais à source comme prototype
Object.setPrototypeOf(relais, source);
// Maintenant relais peut accéder aux méthodes de source.

console.log(relais.signal()); // "Signal émis." (sa propre méthode)
console.log(relais.méthodeSecrète()); // "Message codé." (trouvée dans le prototype)

// Le mécanisme : si JS ne trouve pas sur l'objet, il remonte la chaîne.

Object.create() - Créer un Objet Avec un Prototype Défini

// Plutôt que modifier après, on peut créer avec le bon prototype d'emblée.
let base = {
  vérifier() {
    return "Identité confirmée.";
  }
};

let agent = Object.create(base); // agent a base comme prototype.
agent.action = function() { return "En mission." };

console.log(agent.action()); // "En mission."
console.log(agent.vérifier()); // "Identité confirmée." (héritée)

instanceof - Vérifier la Lignée

function Source() {}
let message = new Source();

console.log(message instanceof Source); // true
console.log(message instanceof Object);  // true (tout hérite d'Object)

// Attention : instanceof vérifie la chaîne de prototypes, pas le type brut.

(Note : Les constructeurs de fonctions à l'ancienne et la manipulation directe de prototype sont des patterns historiques. Aujourd'hui, on utilise les classes. Mais savoir qu'ils existent permet de lire du code ancien.)


8.2 Classes ES6+ - La Façade Moderne

Déclaration de Classe

// Une syntaxe enfin lisible.
class Contact {
  constructor(alias, niveau) {
    this.alias = alias;
    this.niveau = niveau;
    this.missions = 0; // Propriété par défaut
  }
  
  // Méthode d'instance
  envoyerMessage() {
    return `${this.alias} transmet : situation sous contrôle.`;
  }
  
  terminerMission(difficulté) {
    this.missions += difficulté;
    return `Mission terminée. Score total : ${this.missions}`;
  }
}

// Utilisation
let contactAlpha = new Contact("Alpha", 7);
console.log(contactAlpha.envoyerMessage()); // "Alpha transmet : situation sous contrôle."
console.log(contactAlpha.terminerMission(3)); // "Mission terminée. Score total : 3"

Héritage avec extends et super

// Une classe parent
class Agent {
  constructor(codename) {
    this.codename = codename;
  }
  
  rapporter() {
    return `${this.codename} rapporte.`;
  }
}

// Une classe enfant qui hérite
class Infiltré extends Agent {
  constructor(codename, couverture) {
    super(codename); // Appelle le constructeur parent
    this.couverture = couverture;
  }
  
  écouter() {
    return `${this.codename} (${this.couverture}) est à l'écoute.`;
  }
  
  // On peut redéfinir (overrider) une méthode parent
  rapporter() {
    return `${super.rapporter()} Canal sécurisé.`;
  }
}

let shadow = new Infiltré("Shadow", "portier");
console.log(shadow.écouter());   // "Shadow (portier) est à l'écoute."
console.log(shadow.rapporter()); // "Shadow rapporte. Canal sécurisé."

// Vérification de la lignée
console.log(shadow instanceof Infiltré);  // true
console.log(shadow instanceof Agent); // true

8.3 Propriétés et Méthodes de l'Ombre

Getters et Setters - Contrôler l'Accès

class Dossier {
  constructor(nom, classification) {
    this._nom = nom; // Convention : _ pour "interne"
    this._classification = classification;
  }
  
  // Getter (accès comme une propriété)
  get nom() {
    return this._nom.toUpperCase();
  }
  
  // Setter (avec validation)
  set classification(niveau) {
    if (['confidentiel', 'secret', 'top secret'].includes(niveau)) {
      this._classification = niveau;
    } else {
      console.log("Niveau de classification invalide.");
    }
  }
  
  get classification() {
    return this._classification;
  }
  
  // Propriété calculée
  get estAccèsRestreint() {
    return this._classification === 'top secret';
  }
}

let dossierX = new Dossier("Projet Phoenix", "secret");
console.log(dossierX.nom);      // "PROJET PHOENIX"
console.log(dossierX.estAccèsRestreint); // false

dossierX.classification = 'top secret'; // setter
dossierX.classification = 'public'; // "Niveau de classification invalide."

Méthodes et Propriétés Statiques - Pour la Classe, Pas l'Instance

class Chiffrement {
  // Propriété statique (liée à la classe elle-même)
  static ALGORITHME = "AES-256";
  
  // Méthode statique (appelée sur la classe, pas une instance)
  static générerClé() {
    return Math.random().toString(36).substring(2, 15);
  }
  
  // Méthode d'instance (besoin d'un objet)
  coder(message) {
    return `[CODÉ:${message}]`;
  }
}

// Appel des méthodes statiques (SANS `new`)
console.log(Chiffrement.ALGORITHME); // "AES-256"
console.log(Chiffrement.générerClé()); // "a7b9c3d" (exemple)

// Impossible d'appeler statique sur instance
let codeur = new Chiffrement();
console.log(codeur.coder("alerte")); // "[CODÉ:alerte]"
// codeur.générerClé(); // ❌ ERREUR !

Propriétés Privées (#) - Le Vrai Secret

class Coffre {
  #contenu = ''; // Propriété privée (vraiment inaccessible de l'extérieur)
  
  constructor(lieu) {
    this.lieu = lieu; // Public
  }
  
  déposer(objet) {
    this.#contenu = objet;
  }
  
  retirer() {
    const objet = this.#contenu;
    this.#contenu = '';
    return objet;
  }
  
  // Getter pour vérifier (lecture seulement)
  get vide() {
    return this.#contenu === '';
  }
}

let cache = new Coffre("ponton 7");
cache.déposer("microfilms");
console.log(cache.vide); // false

// Impossible d'accéder directement
// console.log(cache.#contenu); // ❌ ERREUR Syntaxique !
// cache.#contenu = "autre chose";   // ❌ IMPOSSIBLE !

Comprendre this - Le Dernier Témoin

class Témoin {
  constructor(pseudo) {
    this.pseudo = pseudo;
  }
  
  identifier() {
    console.log(`Je suis ${this.pseudo}.`);
  }
  
  identifierPlusTard() {
    // Piège classique : `this` est perdu dans le callback.
    setTimeout(function() {
      console.log(`Je suis ${this.pseudo}.`); // ❌ this = global
    }, 1000);
  }
  
  // Solutions :
  identifierAvecPrécaution() {
    // 1. Capturer `this` dans une variable.
    let moi = this;
    setTimeout(function() {
      console.log(`Je suis ${moi.pseudo}.`);
    }, 1000);
  }
  
  identifierAvecFlèche() {
    // 2. Fonction fléchée (garde le `this` du contexte parent).
    setTimeout(() => {
      console.log(`Je suis ${this.pseudo}.`); // ✅ this = instance
    }, 1000);
  }
}

let témoin7 = newmoin("Sept");
témoin7.identifier(); // "Je suis Sept."
témoin7.identifierPlusTard(); // "Je suis undefined."
témoin7.identifierAvecFlèche(); // "Je suis Sept." (après 1s)

call, apply, bind - Forcer l'Identité

let cible1 = { ville: "Detroit" };
let cible2 = { ville: "Chicago" };

function localiser(période, risque) {
  console.log(`${this.ville} - ${période} - Risque ${risque}`);
}

// 1. call() : appelle avec un `this` spécifique.
localiser.call(cible1, "nuit", "élevé"); // "Detroit - nuit - Risque élevé"

// 2. apply() : pareil mais arguments dans un tableau.
localiser.apply(cible2, ["jour", "moyen"]); // "Chicago - jour - Risque moyen"

// 3. bind() : crée une nouvelle fonction avec `this` figé.
let surveillerDetroit = localiser.bind(cible1);
surveillerDetroit("aube", "faible"); // "Detroit - aube - Risque faible"

Résumé de ce module :

  1. Prototypes : La mécanique réelle de l'héritage en JS. Utile pour comprendre les traces anciennes.
  2. Classes ES6 : La syntaxe moderne. Utilise-la pour tout ton nouveau code.
  3. Getters/setters : Pour valider ou masquer les données à l'accès.
  4. Statique vs Instance : Chiffrement.générerClé() vs codeur.coder().
  5. Propriétés privées (#) : Pour enterrer vraiment des informations.
  6. this : Le témoin volatil. Les fonctions fléchées sont ton alibi.
  7. call/apply/bind : Pour les rares cas où tu dois substituer une identité.

La POO, c'est comme monter une cellule dormante :

  • Avant : Des agents libres, agissant dans le désordre.
  • Après : Chaque agent a un rôle, un dossier, des contacts définis. Le chaos devient réseau.

Ça demande de penser différemment. Mais une fois le réseau en place, il est beaucoup plus simple à contrôler. Et à faire disparaître.