« est un » ou « a un » ?
À la leçon précédente, l'héritage disait « est un » : un CompteEpargne est un Compte. Mais réfléchis à une voiture et un moteur. Une voiture n'est pas un moteur. Une voiture a un moteur. Faire « Voiture hérite de Moteur » serait absurde.
Quand la relation est « a un », on n'hérite pas : on compose. L'objet contient d'autres objets comme propriétés, et leur délègue le travail. C'est la composition, et c'est le réflexe que préfèrent les pros.
L'idée : un objet en contient d'autres
Une Voiture a un Moteur (et des Roues, une Radio…). Chacun est un objet à part entière, avec sa propre logique. La voiture ne refait pas le travail du moteur : quand on la démarre, elle demande au moteur de démarrer. On appelle ça déléguer.
En code : contenir et déléguer
Une voiture a besoin d'un moteur. On hésite entre deux approches : class Voiture extends Moteur (héritage) ou class Voiture { constructor(m){ this.moteur = m } } (composition). Avant de dérouler : laquelle choisir, et pourquoi ? Et si demain on veut passer d'un moteur thermique à un moteur électrique sans réécrire Voiture, quelle approche le permet le plus facilement ?
Voir la réponse
Voiture extends Moteur est une erreur de modélisation : une voiture n'est pas un moteur, elle a un moteur. L'héritage exprime « est un » ; la composition exprime « a un ». Avec la composition, la Voiture reçoit un moteur via son constructeur et lui délègue demarrer(). Pour passer au moteur électrique, il suffit d'injecter un MoteurElectrique qui expose la même méthode : la classe Voiture ne change pas. Avec l'héritage, le changement forcerait une refonte. Règle d'or : quand la relation est « a un », préférer la composition.
classe MoteurEssence:
public demarrer(): retourne "Vroum vroum !"
classe Voiture:
privé moteur
constructeur(moteur):
this.moteur = moteur // la Voiture A UN moteur
public demarrer():
retourne this.moteur.demarrer() // elle DÉLÈGUE au moteur
voiture = new Voiture(new MoteurEssence())
voiture.demarrer() // "Vroum vroum !"
Le gros avantage : on peut échanger une pièce sans rien casser. Donne un MoteurElectrique à la place, et la voiture roule pareil, en silence. Avec l'héritage, ce genre de changement à chaud est bien plus rigide.
Le même mécanisme, en vrai JavaScript. Lance le code, observe la délégation, puis échange le moteur et relance : la classe Voiture ne change pas, son comportement oui.
class Moteur {
demarrer() { return "Vroum !"; }
}
class MoteurElectrique {
demarrer() { return "... (silencieux)"; }
}
class Voiture {
constructor(moteur) {
this.moteur = moteur; // la Voiture A UN moteur
}
demarrer() {
return this.moteur.demarrer(); // délégation
}
}
// Composition : on injecte le moteur thermique
const maVoiture = new Voiture(new Moteur());
console.log("Voiture (thermique) :", maVoiture.demarrer());
// On échange le moteur sans toucher à la classe Voiture
maVoiture.moteur = new MoteurElectrique();
console.log("Voiture (électrique) :", maVoiture.demarrer());
Composition ou héritage ? Le bon réflexe
- « est un » (CompteEpargne est un Compte) → héritage. Plus rare qu'on ne croit.
- « a un » (Voiture a un Moteur) → composition. C'est le cas le plus fréquent.
Règle des pros : « préférer la composition à l'héritage ». La composition est souple (on assemble et on remplace des pièces), l'héritage est rigide (l'enfant est soudé au parent). Dans le doute, demande-toi « est-ce un » vs « a un » : la plupart du temps, c'est « a un », donc compose.
La composition en vrai (essaie, et change le moteur)
Une Voiture qui a un Moteur. Démarre-la (elle délègue au moteur), puis échange le moteur : la voiture ne change pas, son comportement oui. Impossible aussi proprement avec de l'héritage.
"is a" or "has a"?
In the previous lesson, inheritance said "is a": a CompteEpargne is a Compte. But think of a car and an engine. A car is not an engine. A car has an engine. Writing "Car extends Engine" would be absurd.
When the relationship is "has a", you don't inherit: you compose. The object contains other objects as properties, and delegates the work to them. That's composition, and it's the reflex the pros prefer.
The idea: an object contains others
A Voiture (car) has a Moteur (engine) (and Roues wheels, a Radio…). Each is a full-fledged object with its own logic. The car doesn't redo the engine's work: when you start it, it asks the engine to start. We call that delegating.
In code: contain and delegate
A car needs an engine. Two approaches: class Car extends Engine (inheritance) or class Car { constructor(e){ this.engine = e } } (composition). Before you expand: which should you pick, and why? And if tomorrow you want to switch from a combustion engine to an electric one without rewriting Car, which approach makes that easiest?
See the answer
Car extends Engine is a modeling mistake: a car is not an engine, it has one. Inheritance expresses "is a"; composition expresses "has a". With composition, Car receives an engine via its constructor and delegates start() to it. To switch to an electric engine, you just inject an ElectricEngine that exposes the same method: the Car class is untouched. With inheritance, that change would force a redesign. Golden rule: when the relationship is "has a", favor composition.
class MoteurEssence:
public demarrer(): return "Vroum vroum !"
class Voiture:
private moteur
constructor(moteur):
this.moteur = moteur // the Voiture HAS A moteur
public demarrer():
return this.moteur.demarrer() // it DELEGATES to the engine
voiture = new Voiture(new MoteurEssence())
voiture.demarrer() // "Vroum vroum !"
The big upside: you can swap a part without breaking anything. Give it a MoteurElectrique instead, and the car drives the same, silently. With inheritance, that kind of hot-swap is far more rigid.
The same mechanism, in real JavaScript. Run the code, observe the delegation, then swap the engine and run again: the Car class doesn't change, its behavior does.
class Engine {
start() { return "Vroom!"; }
}
class ElectricEngine {
start() { return "... (silent)"; }
}
class Car {
constructor(engine) {
this.engine = engine; // the Car HAS AN engine
}
start() {
return this.engine.start(); // delegation
}
}
// Composition: inject the combustion engine
const myCar = new Car(new Engine());
console.log("Car (combustion):", myCar.start());
// Swap the engine without touching the Car class
myCar.engine = new ElectricEngine();
console.log("Car (electric):", myCar.start());
Composition or inheritance? The right reflex
- "is a" (CompteEpargne is a Compte) → inheritance. Rarer than you'd think.
- "has a" (Voiture has a Moteur) → composition. The most frequent case.
The pros' rule: "favor composition over inheritance". Composition is flexible (you assemble and replace parts), inheritance is rigid (the child is welded to the parent). When in doubt, ask "is a" vs "has a": most of the time it's "has a", so compose.
Composition for real (try it, and swap the engine)
A Voiture that has a Moteur. Start it (it delegates to the engine), then swap the engine: the car doesn't change, its behavior does. Also impossible this cleanly with inheritance.
🎯 Pratique
S'entraîner (clique pour ouvrir) :
⚖️ Juge le code de l'IA
Tu demandes à l'IA de modéliser une voiture et son moteur. Elle te propose cette conception. Ton rôle de relecteur : l'accepter telle quelle ou la rejeter, et dire pourquoi.
class Moteur {
demarrer() { return "Vroum vroum !"; }
}
// L'IA fait hériter la voiture du moteur
class Voiture extends Moteur {
rouler() { return this.demarrer() + " la voiture avance"; }
}
Voiture extends Moteur dit « une voiture est un moteur » : c'est faux, une voiture a un moteur. Le code « marche » (la voiture peut appeler demarrer()), mais la relation est mal modélisée et ça coûte cher ensuite : impossible d'avoir deux moteurs, impossible de changer de moteur à chaud, et la voiture hérite de tout le moteur même de ce qui ne la regarde pas. La bonne conception, c'est la composition : class Voiture { constructor(moteur){ this.moteur = moteur; } }, puis this.moteur.demarrer(). On garde la possibilité d'échanger la pièce.🧠 Rappel libre
Sans remonter dans la leçon : quel test mental distingue une relation qui demande de l'héritage d'une relation qui demande de la composition, et qu'est-ce que déléguer veut dire concrètement ?
CompteEpargne est un Compte) ; « a un » → composition (une Voiture a un Moteur). La plupart des relations sont des « a un », donc on compose plus souvent qu'on n'hérite. Déléguer, c'est ne pas refaire le travail soi-même : voiture.demarrer() appelle en réalité this.moteur.demarrer(). La voiture contient le moteur comme propriété et lui passe la tâche.class Voiture { constructor(m) { this.moteur = m; } }. Quelle relation ce code exprime-t-il ?class Voiture qui compose un moteur et lui délègue demarrer(), pourquoi peut-on passer d'un MoteurEssence à un MoteurElectrique sans toucher à Voiture ?Tu as monté deux moteurs différents et appelé la même méthode demarrer() sur chacun, avec un résultat différent. Ce petit miracle a un nom : le polymorphisme. C'est le dernier pilier.
Leçon 6 : Le polymorphisme →