Retour au cours

frontend / typescript

Classes typées

Leçon 101 exercice

Explication

Ce que vous allez apprendre

  • Utiliser les modificateurs de visibilité public, protected et private
  • Déclarer et assigner une propriété directement dans les paramètres du constructeur
  • Créer une classe abstraite avec des méthodes que les sous-classes doivent implémenter
  • Expliquer pourquoi private disparaît à l'exécution, contrairement à #champ
  • Choisir entre private (TypeScript) et #champ (natif) selon le niveau de garantie voulu

Dans quel contexte ?

Lors d'un audit de sécurité, un développeur découvre qu'un champ private numeroCarte: string sur une classe Paiement est en réalité lisible depuis n'importe où dans le code compilé, via (instancePaiement as any).numeroCarte. Le mot-clé private de TypeScript n'est qu'une vérification au moment de la compilation : une fois transpilé en JavaScript, cette protection disparaît complètement, et rien n'empêche techniquement d'y accéder avec un simple cast. Remplacer private numeroCarte par #numeroCarte (champ privé natif de JavaScript, vu à la leçon 6 du cours JavaScript) rend l'accès réellement impossible, y compris à l'exécution.

Les classes, avec des garanties en plus

Les classes existent déjà en JavaScript moderne, mais TypeScript leur ajoute une couche de vérification : visibilité des propriétés, obligation d'implémenter certaines méthodes, contrats vérifiés à la compilation. Cette leçon reprend les briques classiques de la programmation orientée objet — héritage, classes abstraites, interfaces implémentées — en montrant comment TypeScript les rend plus sûres.

Prérequis

Une bonne connaissance des classes ES6 (constructeur, extends, super) est nécessaire pour cette leçon : TypeScript ajoute des garanties par-dessus ces mécanismes, sans les remplacer.

Les modificateurs de visibilité

public (le défaut), protected et private contrôlent qui peut accéder à une propriété ou une méthode : depuis n'importe où, seulement depuis la classe et ses sous-classes, ou seulement depuis l'intérieur de la classe elle-même. Un raccourci pratique permet de déclarer ET d'assigner une propriété directement dans la liste des paramètres du constructeur, évitant une répétition inutile.

ModificateurAccessible depuisVérifié à l'exécution ?
public (défaut)N'importe oùNon applicable
protectedLa classe et ses sous-classesNon (compilation seulement)
privateLa classe uniquementNon (compilation seulement)
#champ (natif)La classe uniquementOui, réellement bloqué

Classes abstraites : un modèle incomplet par design

Une classe abstraite ne peut jamais être instanciée directement — elle sert de modèle que les sous-classes doivent compléter en implémentant les méthodes marquées abstract. C'est une façon d'imposer un contrat de conception : toute voiture doit savoir démarrer, mais on ne sait pas encore comment chaque type de voiture le fait précisément.

Piège fréquent

private et protected ne protègent que contre des erreurs de programmation détectées à la compilation, jamais contre un accès volontaire à l'exécution ((instance as any).champPrive). Pour une donnée réellement sensible, utilisez toujours un champ privé natif #champ.

Private TypeScript contre champs privés natifs

Un détail à ne pas manquer : le private de TypeScript n'est qu'une vérification à la compilation — elle disparaît totalement dans le JavaScript généré et peut être contournée avec un cast vers any. Les champs privés natifs de JavaScript, préfixés par #, sont eux réellement inaccessibles depuis l'extérieur de la classe, même à l'exécution. Pour une confidentialité réellement garantie, préférez #champ à private.

Commandes & code

Classes typées

ts
abstract class Vehicule {
  // modifiers : public (défaut), private, protected, readonly
  public readonly id: string;
  protected vitesse: number = 0;
  private numeroSerie: string;

  constructor(id: string, numeroSerie: string) {
    this.id = id;
    this.numeroSerie = numeroSerie;
  }

  // Méthode abstraite : DOIT être implémentée par les sous-classes
  abstract demarrer(): void;

  // Méthode concrète, héritée telle quelle
  accelerer(delta: number): void {
    this.vitesse += delta;
  }

  protected getNumeroSerie(): string {
    return this.numeroSerie;
  }
}

class Voiture extends Vehicule {
  constructor(id: string, numeroSerie: string, public nbPortes: number) {
    // "public nbPortes" dans le constructeur = raccourci qui déclare ET assigne la propriété
    super(id, numeroSerie);
  }

  demarrer(): void {
    console.log(`Voiture ${this.id} (série ${this.getNumeroSerie()}) démarre`);
  }
}

// const v = new Vehicule("v1", "abc"); // Error : impossible d'instancier une classe abstraite
const voiture = new Voiture("v2", "XYZ123", 4);

// Interface implémentée par une classe : contrat vérifié à la compilation
interface Chronometrable {
  demarrerChrono(): void;
  arreterChrono(): number;
}

class Course implements Chronometrable {
  private debut = 0;
  demarrerChrono(): void {
    this.debut = Date.now();
  }
  arreterChrono(): number {
    return Date.now() - this.debut;
  }
}

// Getters / setters typés
class CompteBancaire {
  #solde = 0; // champ privé natif JS (invisible même via TS)

  get solde(): number {
    return this.#solde;
  }
  set solde(valeur: number) {
    if (valeur < 0) throw new Error("Solde négatif interdit");
    this.#solde = valeur;
  }
}

// Propriétés et méthodes statiques
class CompteurGlobal {
  static instances = 0;
  static creer(): CompteurGlobal {
    CompteurGlobal.instances++;
    return new CompteurGlobal();
  }
}

// Classe générique
class Boite<T> {
  constructor(private contenu: T) {}
  ouvrir(): T {
    return this.contenu;
  }
}
const boiteString = new Boite<string>("cadeau");

Résumé

  • public/protected/private dans le constructeur déclare ET assigne la propriété.
  • abstract interdit l'instanciation directe et force l'implémentation dans les sous-classes.
  • #champ (privé natif JS) est plus strict que private (TS) : invisible même via any.

Exercices pratiques

1 disponible
1

Mission : sécuriser réellement numeroCarte

Objectif : Comprendre pourquoi private ne protège pas à l'exécution, puis migrer un champ sensible vers un champ privé natif réellement inaccessible.

Contexte

Un audit de sécurité découvre qu'un champ private numeroCarte: string sur une classe Paiement est en réalité lisible depuis n'importe où via (instancePaiement as any).numeroCarte. Ta mission : comprendre pourquoi ce contournement fonctionne, puis rendre ce champ réellement inaccessible, y compris à l'exécution.

Résoudre l’exercice →