Lesson 5/6 9 min

Composition vs inheritance

The pros' reflex: assemble objects ("has a") rather than inherit everything ("is a"). When to pick what, with an interactive demo.

FR EN

« 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.

Une Voiture contient un Moteur et des Roues comme propriétés : c'est la composition, une relation « a un », par opposition à l'héritage « est un ». Voiture « a un » Moteur (composition) objet Voiture Moteur demarrer() Roues tourner() voiture.demarrer() → délègue au moteur La voiture assemble des objets et leur délègue le travail.
Composer, c'est assembler des objets autonomes et leur déléguer, plutôt que tout faire hériter.

En code : contenir et déléguer

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.

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.

Moteur monté :

Une voiture et un moteur, c'est une relation…
Pourquoi les pros préfèrent souvent la composition ?
Prochaine étape

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 →

"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.

A Voiture contains a Moteur and Roues as properties: that's composition, a "has a" relationship, as opposed to "is a" inheritance. Voiture "has a" Moteur (composition) Voiture object Moteur demarrer() Roues tourner() voiture.demarrer() → delegates to the engine The car assembles objects and delegates the work to them.
Composing means assembling autonomous objects and delegating to them, rather than inheriting everything.

In code: contain and delegate

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.

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.

Engine mounted:

Next step

You mounted two different engines and called the same demarrer() method on each, with a different result. That little miracle has a name: polymorphism. It's the last pillar.

Lesson 6: polymorphism →
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 ?

Le test : « est un » → héritage (un 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.
Accepter ou rejeter la conception 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"; }
}
À rejeter. 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.
Besoin d'un développeur pour votre projet ?

Réponse sous 24h · Sans engagement