Menu
Coddy logo textTech

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.

challenge icon

Défi

Facile

Construisons 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) et speedGHz (double). Incluez un constructeur pour initialiser les deux champs et des méthodes getter pour chacun. Ajoutez une méthode process() qui retourne une String : [brand] processing at [speedGHz] GHz
  • Memory.java : Créez une classe représentant la RAM. Une Memory possède deux champs privés : type (String, comme "DDR4" ou "DDR5") et sizeGB (int). Incluez un constructeur, des getters et une méthode load() qui retourne : Loading [sizeGB]GB [type] memory
  • Computer.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éthode boot() qui retourne une String multi-ligne montrant l'ordinateur qui démarre en utilisant ses composants :
    Booting computer...
    [processor.process() result]
    [memory.load() result]
    System ready!
    Ajoutez également une méthode 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 de getSpecs().

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()
    }
}
quiz iconTestez-vous

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