Un même mot, des réponses différentes
À la leçon précédente, tu as appelé demarrer() sur deux moteurs différents : l'un faisait « Vroum », l'autre le silence. Le même appel, des comportements différents. Ce petit miracle a un nom : le polymorphisme (du grec « plusieurs formes »).
L'idée : plusieurs objets différents comprennent le même message (la même méthode), et chacun y répond à sa manière. Celui qui appelle n'a pas besoin de savoir à qui il parle : il envoie le message, l'objet se débrouille.
L'exemple qui parle : le zoo
Imagine des animaux : un chien, un chat, un canard. Tous savent parler(), mais le résultat diffère. Si tu as une liste d'animaux et que tu veux les faire crier, tu écris une seule boucle qui appelle parler() sur chacun, sans te soucier de l'espèce :
parler() ; chaque objet répond à sa façon.classe Chien: public parler(): retourne "Wouf !"
classe Chat: public parler(): retourne "Miaou !"
classe Canard: public parler(): retourne "Coin coin !"
animaux = [new Chien(), new Chat(), new Canard()]
// UNE seule boucle, qui ignore l'espèce : c'est ça, le polymorphisme
pour chaque a dans animaux:
afficher(a.parler()) // "Wouf !", "Miaou !", "Coin coin !"
La boucle ne contient aucun si c'est un chien… sinon si c'est un chat…. Elle envoie parler() et fait confiance à chaque objet pour répondre correctement.
Pourquoi c'est puissant : le contrat
Pour que ça marche, tous les animaux doivent respecter le même contrat : « je sais parler() ». Ce contrat partagé s'appelle une interface.
Concrètement, une interface est un contrat nu : la liste des méthodes qu'une classe promet de fournir, sans dire comment. Le contrat « Animal » tient ici en une ligne, « tout Animal sait parler() » :
On a trois classes Chien, Chat et Vache, chacune avec sa méthode parler(). On les met dans un tableau [new Chien(), new Chat(), new Vache()] et on boucle avec for (const animal of animaux) console.log(animal.parler()). Avant de dérouler : qu'affiche la boucle, et pourquoi le même appel animal.parler() donne-t-il des résultats différents sans un seul if sur le type ? Que se passe-t-il si on ajoute une classe Mouton ?
Voir la réponse
La boucle affiche Wouf !, Miaou !, Meuh ! : chaque objet exécute sa version de parler(), choisie à l'exécution selon son type réel (dispatch dynamique). Le code appelant ne connaît qu'un contrat, « tout animal sait parler » ; il n'a pas à savoir lequel. Ajouter Mouton avec sa propre méthode parler() fonctionne sans toucher à la boucle : c'est tout l'intérêt du polymorphisme, extensible et sans if/else géant à maintenir.
interface Animal:
parler() // le contrat : QUOI, jamais COMMENT
classe Chien implémente Animal:
public parler(): retourne "Wouf !" // le COMMENT, propre au Chien
classe Chat implémente Animal:
public parler(): retourne "Miaou !"
L'interface ne contient aucun code exécutable, seulement la promesse. C'est elle qui rend la boucle sûre : si on sait qu'un objet est un Animal, on a la garantie qu'il possède un parler(), donc a.parler() ne plantera pas. Sans contrat, rien ne garantit que l'objet sache répondre au message.
Interface ou classe abstraite ? Une interface n'impose que le contrat : rien que des méthodes à remplir, zéro code fourni. Une classe abstraite peut en plus livrer du code commun déjà écrit, tout en laissant certaines méthodes à compléter par les enfants. Règle simple : juste des promesses à imposer, prends une interface ; tu veux aussi partager du code tout fait, prends une classe abstraite.
Le vrai bénéfice : on ajoute un nouveau cas sans toucher au code existant. Demain tu ajoutes un Mouton qui sait parler() ? La boucle le gère sans une seule modification. C'est ce qui rend un programme extensible : on branche de nouvelles pièces, on ne réécrit pas les anciennes.
Vérifie ta prédiction : lance ce code, observe que chaque objet répond à sa façon, puis ajoute une classe Mouton avec parler() qui retourne "Bêê !" et pousse-la dans le tableau.
class Chien {
parler() { return "Wouf !"; }
}
class Chat {
parler() { return "Miaou !"; }
}
class Vache {
parler() { return "Meuh !"; }
}
const animaux = [new Chien(), new Chat(), new Vache()];
// Une seule boucle, zéro if/else sur le type : c'est le polymorphisme
for (const animal of animaux) {
console.log(animal.parler());
}
// → Wouf !
// → Miaou !
// → Meuh !
Le zoo en vrai (fais-les parler, ajoute un mouton)
Chaque animal est un objet avec sa propre méthode parler(). Le bouton lance une seule boucle sur toute la liste. Ajoute un mouton : la même boucle le fait parler, sans aucune modification.
One word, different answers
In the previous lesson, you called demarrer() on two different engines: one went "Vroum", the other stayed silent. The same call, different behaviors. That little miracle has a name: polymorphism (Greek for "many forms").
The idea: several different objects understand the same message (the same method), and each answers in its own way. The caller doesn't need to know who it's talking to: it sends the message, the object handles it.
The telling example: the zoo
Picture some animals: a dog, a cat, a duck. They all know how to parler() (speak), but the result differs. If you have a list of animals and want to make them all speak, you write a single loop that calls parler() on each, without caring about the species:
parler(); each object answers in its own way.class Chien: public parler(): return "Wouf !"
class Chat: public parler(): return "Miaou !"
class Canard: public parler(): return "Coin coin !"
animaux = [new Chien(), new Chat(), new Canard()]
// ONE loop, ignoring the species: that's polymorphism
for each a in animaux:
print(a.parler()) // "Wouf !", "Miaou !", "Coin coin !"
The loop contains no if it's a dog… else if it's a cat…. It sends parler() and trusts each object to answer correctly.
Why it's powerful: the contract
For this to work, all animals must honor the same contract: "I know how to parler()". That shared contract is called an interface.
Concretely, an interface is a bare contract: the list of methods a class promises to provide, without saying how. The "Animal" contract fits in one line here, "every Animal knows how to parler()":
We have three classes Dog, Cat and Cow, each with its own speak() method. We put them in an array [new Dog(), new Cat(), new Cow()] and loop with for (const animal of animals) console.log(animal.speak()). Before you expand: what does the loop print, and why does the same call animal.speak() produce different results without a single if on the type? What happens if you add a Sheep class?
See the answer
The loop prints Woof!, Meow!, Moo!: each object runs its own version of speak(), chosen at runtime based on its actual type (dynamic dispatch). The calling code only knows one contract, "every animal can speak"; it doesn't need to know which one. Adding Sheep with its own speak() works without touching the loop at all — that's the whole point of polymorphism: extensible, no giant if/else chain to maintain.
interface Animal:
parler() // the contract: WHAT, never HOW
class Chien implements Animal:
public parler(): return "Wouf !" // the HOW, specific to Chien
class Chat implements Animal:
public parler(): return "Miaou !"
The interface holds no runnable code, only the promise. That's what makes the loop safe: if we know an object is an Animal, we are guaranteed it has a parler(), so a.parler() won't crash. Without a contract, nothing guarantees the object can answer the message.
Interface or abstract class? An interface imposes only the contract: nothing but methods to fill in, zero code provided. An abstract class can additionally ship shared, already-written code, while leaving some methods for the children to complete. Simple rule: only promises to enforce, use an interface; you also want to share ready-made code, use an abstract class.
The real benefit: you add a new case without touching the existing code. Tomorrow you add a Mouton (sheep) that knows how to parler()? The loop handles it with zero changes. That's what makes a program extensible: you plug in new parts, you don't rewrite the old ones.
Check your prediction: run this code, watch each object respond in its own way, then add a Sheep class with speak() returning "Baa!" and push it into the array.
class Dog {
speak() { return "Woof!"; }
}
class Cat {
speak() { return "Meow!"; }
}
class Cow {
speak() { return "Moo!"; }
}
const animals = [new Dog(), new Cat(), new Cow()];
// One loop, zero if/else on the type: that's polymorphism
for (const animal of animals) {
console.log(animal.speak());
}
// → Woof!
// → Meow!
// → Moo!
The zoo for real (make them speak, add a sheep)
Each animal is an object with its own parler() method. The button runs a single loop over the whole list. Add a sheep: the same loop makes it speak, with no change at all.
🎯 Pratique
S'entraîner (clique pour ouvrir) :
🧠 Rappel libre
Sans remonter dans la leçon : avec tes mots, qu'est-ce que le polymorphisme, et qu'est-ce que la boucle animaux.forEach(a => a.parler()) n'a justement pas besoin de savoir ?
parler()) auquel chaque objet répond à sa façon : Chien renvoie « Wouf ! », Chat « Miaou ! », etc. La boucle n'a pas besoin de connaître l'espèce de chaque a : elle envoie parler() et fait confiance au contrat partagé. Pas de if c'est un chien… sinon si c'est un chat….⚖️ Juge le code de l'IA
Tu as demandé à l'IA une fonction qui fait crier un animal. Elle te propose ceci. Ton rôle de relecteur : l'accepter telle quelle ou la rejeter, et dire pourquoi.
function faireCrier(animal) {
if (animal.espece === 'chien') return 'Wouf !';
else if (animal.espece === 'chat') return 'Miaou !';
else if (animal.espece === 'canard') return 'Coin coin !';
// ... un else if de plus à chaque nouvel animal
}
if / else if qui teste l'espèce. Le défaut n'est pas qu'elle plante (elle marche), mais qu'elle oblige à rouvrir et modifier cette fonction à chaque nouvel animal. La bonne version donne à chaque classe sa propre méthode parler() et appelle simplement animal.parler() : ajouter un Mouton ne touche plus une ligne du code appelant.animal.parler() sur un objet Chien, puis sur un objet Chat. Les deux renvoient des sons différents. Qu'est-ce qui explique ce comportement ?for (const a of animaux) console.log(a.parler()) sur un tableau contenant un Chien, un Chat et une Vache. On veut ajouter une classe Mouton. Pourquoi peut-on pousser new Mouton() dans le tableau sans toucher à cette boucle ?Tu as vu des objets différents répondre au même message parler(). Mais qui garantit qu'ils savent tous le faire ? L'interface : le papier qui transforme « ça marche » en « c'est garanti ».
Leçon 7 : L'interface, le contrat →