frontend / typescript
Classes typées
Explication
Ce que vous allez apprendre
- Utiliser les modificateurs de visibilité
public,protectedetprivate - 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
privatedisparaî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.
| Modificateur | Accessible depuis | Vérifié à l'exécution ? |
|---|---|---|
public (défaut) | N'importe où | Non applicable |
protected | La classe et ses sous-classes | Non (compilation seulement) |
private | La classe uniquement | Non (compilation seulement) |
#champ (natif) | La classe uniquement | Oui, 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
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/privatedans le constructeur déclare ET assigne la propriété.abstractinterdit l'instanciation directe et force l'implémentation dans les sous-classes.#champ(privé natif JS) est plus strict queprivate(TS) : invisible même viaany.
Exercices pratiques
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.