Composition vs Héritage
Fait partie de la section Programmation Orientée Objet du Journey Java de Coddy — leçon 47 sur 87.
L'héritage crée une relation « est un » : un Dog est un Animal. Mais que se passe-t-il si une classe a besoin des fonctionnalités de quelque chose qu'elle n'est pas ? C'est là qu'intervient la composition, en établissant à la place une relation « a un ».
Avec la composition, une classe contient des instances d'autres classes en tant que champs plutôt que de les étendre :
// Approche par héritage : Car EST un Engine (n'a pas de sens)
class Car extends Engine { }
// Approche par composition : Car A un Engine (a du sens)
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.ignite();
}
}La composition offre plusieurs avantages. Vous pouvez changer l'objet composé au moment de l'exécution, comme par exemple échanger des moteurs.
Vous pouvez également combiner des comportements provenant de plusieurs classes, ce que l'héritage ne permet pas en Java. La classe composée n'expose que les méthodes qu'elle choisit, offrant ainsi une meilleure encapsulation.
Une règle pratique : utilisez l'héritage lorsqu'il existe une véritable relation « est un » et que la sous-classe représente véritablement une version spécialisée du parent.
Utilisez la composition lorsque vous avez besoin d'utiliser les fonctionnalités d'une autre classe sans être de ce type. De nombreux développeurs expérimentés suivent le principe : « privilégier la composition plutôt que l'héritage » car cela conduit à un code plus flexible et plus facile à maintenir.
Défi
FacileConstruisons un système informatique en utilisant la composition pour démontrer la relation "a-un" (has-a). Au lieu qu'un Computer hérite de ses composants (ce qui n'aurait pas de sens), votre ordinateur va contenir les pièces dont il a besoin pour fonctionner.
Vous organiserez votre code sur trois fichiers :
Processor.java: Créez une classe représentant un processeur. Un Processor possède deux champs privés :brand(String) etspeedGHz(double). Incluez un constructeur pour initialiser les deux champs et des méthodes getter pour chacun. Ajoutez une méthodeprocess()qui retourne une String :[brand] processing at [speedGHz] GHzMemory.java: Créez une classe représentant la RAM. Une Memory possède deux champs privés :type(String, comme "DDR4" ou "DDR5") etsizeGB(int). Incluez un constructeur, des getters et une méthodeload()qui retourne :Loading [sizeGB]GB [type] memoryComputer.java: C'est ici que la composition brille ! Votre classe Computer doit avoir un Processor et une Memory comme champs privés — elle ne les étend pas, elle les contient. Incluez un constructeur qui accepte les deux composants. Ajoutez une méthodeboot()qui retourne une String multi-ligne montrant l'ordinateur qui démarre en utilisant ses composants :Ajoutez également une méthodeBooting computer... [processor.process() result] [memory.load() result] System ready!getSpecs()qui retourne :Specs: [processor brand] CPU, [memory sizeGB]GB [memory type]Main.java: Donnez vie à votre système composé ! Vous recevrez quatre entrées : la marque du processeur (String), la vitesse du processeur (double), le type de mémoire (String) et la taille de la mémoire (int).Créez un Processor et une Memory avec ces valeurs, puis composez-les dans un Computer. Affichez le résultat de l'appel à
boot(), puis affichez le résultat degetSpecs().
Vous recevrez quatre entrées dans l'ordre : la marque du processeur, la vitesse du processeur (GHz), le type de mémoire et la taille de la mémoire (GB).
Remarquez comment le Computer délègue le travail à ses composants plutôt que d'essayer d'être eux — c'est toute la puissance de la composition ! Le Computer peut utiliser n'importe quel Processor ou Memory que vous lui donnez, ce qui rend la conception flexible et réaliste.
Aide-mémoire
La composition crée une relation « a un » (has-a) où une classe contient des instances d'autres classes en tant que champs plutôt que de les étendre.
Héritage vs Composition :
// Héritage : Car EST UN Engine (cela n'a pas de sens)
class Car extends Engine { }
// Composition : Car A UN Engine (cela a du sens)
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
public void start() {
engine.ignite();
}
}Avantages de la composition :
- Les objets peuvent être modifiés au moment de l'exécution
- Peut combiner des comportements provenant de plusieurs classes
- Meilleure encapsulation - n'expose que les méthodes choisies
- Code plus flexible et facile à maintenir
Quand l'utiliser :
- Héritage : À utiliser lorsqu'il existe une véritable relation « est un » (is-a) et que la sous-classe est une version spécialisée du parent
- Composition : À utiliser lorsque vous avez besoin de la fonctionnalité d'une autre classe sans être de ce type
Bonne pratique : « Privilégiez la composition à l'héritage »
Essayez vous-même
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Lire les entrées
String processorBrand = scanner.nextLine();
double processorSpeed = Double.parseDouble(scanner.nextLine());
String memoryType = scanner.nextLine();
int memorySize = Integer.parseInt(scanner.nextLine());
// TODO: Créer un Processor avec la marque et la vitesse
// TODO: Créer une Memory avec le type et la taille
// TODO: Créer un Computer en composant le Processor et la Memory
// TODO: Afficher le résultat de boot()
// TODO: Afficher le résultat de getSpecs()
}
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation Orientée Objet
1Fondamentaux de la POO
Fichiers externesIntroduction à la POOClasses vs ObjetsLe mot-clé thisMéthodesChamps (Attributs)Méthode constructeurSurcharge de constructeurRécapitulatif - Calculatrice simple4Héritage
Héritage de base (extends)Le mot-clé superRedéfinition de méthode (@Override)Chaînage de constructeursLa classe ObjectHéritage simple et multiniveauPourquoi pas d'héritage multiple de classesRécapitulatif - Hiérarchie des employés7Méthodes spéciales et classe Object
Méthode toString()equals() et hashCode()Méthode clone()compareTo() et ComparableInterface ComparatorRécapitulatif - Tri personnalisé2Modificateurs d'accès et Encapsulation
Aperçu des niveaux d'accèsMéthodes Getter et SetterMasquage d'informationsLe mot-clé finalRécapitulatif - Gestionnaire de compte bancaire5Polymorphisme
Bases de la surcharge de méthodesRedéfinition de méthodes (Run-Time)Upcasting et DowncastingL'opérateur instanceofClasses et méthodes abstraitesRécapitulatif - Calculateur de formes8Concepts avancés de la POO
Composition vs HéritageAgrégation vs CompositionClasses internes, imbriquées et anonymesEnums et méthodes d'EnumRecords (Java 16+)Classes scellées (Java 17+)11Patrons de conception, partie 1
Introduction aux patrons de conceptionPatron SingletonPatron FabriquePatron MonteurPatron ObservateurPatron Stratégie3Propriétés de classe et membres statiques
Variables d'instance vs variables statiquesMéthodes statiquesBlocs statiquesConstantes (static final)Récapitulatif - Compteur et utilitaire6Interfaces et Classes Abstraites
Introduction aux InterfacesImplémentation d'InterfacesImplémentation d'Interfaces MultiplesDefault et Static dans les InterfacesClasses Abstraites vs InterfacesInterfaces FonctionnellesRécapitulatif - Système de Paiement9La généricité
Introduction à la généricitéClasses génériquesMéthodes génériquesParamètres de type bornésWildcards (?, extends, super)Récapitulatif - Conteneur générique12Patrons de conception, partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositePatron Itérateur