Composition vs Héritage
Fait partie de la section Programmation Orientée Objet du Journey Java de Coddy. Leçon 47 sur 87.
L’Inheritance crée une relation « est-un » : un Dog est un Animal. Mais que se passe-t-il si une classe a besoin de fonctionnalités provenant de quelque chose qu’elle n’est pas ? C’est là qu’intervient la composition, qui établit plutôt une relation « possède-un ».
Avec la composition, une classe contient des instances d’autres classes en tant que champs plutôt que d’en hériter :
// 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 modifier l’objet composé à l’exécution, par exemple en échangeant les moteurs.
Vous pouvez également combiner les comportements 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, ce qui assure un meilleur encapsulage.
Une règle pratique : utilise l’héritage lorsqu’il existe une véritable relation « est-un » et que la sous-classe représente réellement une version spécialisée de la classe parente.
Utilisez la composition lorsque vous devez utiliser les fonctionnalités d’une autre classe sans être de ce type. De nombreux développeurs expérimentés suivent le principe suivant : "favoriser la composition plutôt que l’héritage", car cela permet d’obtenir un code plus flexible et plus facile à maintenir.
Défi
FacileConstruisons un système informatique en utilisant la composition pour démontrer la relation « has-a ». Au lieu qu'un Computer hérite de ses composants (ce qui n'aurait pas de sens), votre ordinateur contiendra les éléments dont il a besoin pour fonctionner.
Vous organiserez votre code sur trois fichiers :
Processor.java: créez une classe représentant un CPU. Un Processor possède deux champs privés :brand(String) etspeedGHz(double). Ajoutez un constructeur pour initialiser les deux champs ainsi que des méthodes getter pour chacun d'eux. Ajoutez une méthodeprocess()qui renvoie 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). Ajoutez un constructeur, des getters et une méthodeload()qui renvoie :Loading [sizeGB]GB [type] memoryComputer.java: c'est ici que la composition révèle toute sa puissance ! Votre classe Computer doit posséder un Processor et une Memory en tant que champs privés. Elle n'en hérite pas : elle les contient. Ajoutez un constructeur qui accepte les deux composants. Ajoutez une méthodeboot()qui renvoie une String multiligne montrant le démarrage de l'ordinateur en utilisant ses composants :Ajoutez également une méthodeBooting computer... [processor.process() result] [memory.load() result] System ready!getSpecs()qui renvoie :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 suivant : 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 au lieu 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 fournissez, ce qui rend la conception flexible et réaliste.
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érateurEntraînez-vous par vous-même : Compilateur Java en ligne