Menu
Coddy logo textTech

Composition vs Héritage

Fait partie de la section Programmation Orientée Objet du Journey Dart de Coddy — leçon 82 sur 110.

Vous avez appris que l'héritage crée une relation « est un » - un Dog est un Animal. Mais il existe une autre approche puissante : la composition, qui crée une relation « a un ». Au lieu d'hériter d'un comportement, une classe contient des instances d'autres classes et leur délègue le travail.

Considérez une classe Car. Avec l'héritage, vous pourriez essayer d'étendre une classe Engine - mais une voiture n'est pas un moteur, elle a un moteur :

class Engine {
  void start() => print('Engine started');
}

class Car {
  final Engine _engine = Engine();  // Composition : La voiture A UN moteur
  
  void start() {
    _engine.start();  // Déléguer au moteur
    print('Car is ready to drive');
  }
}

void main() {
  var car = Car();
  car.start();
}

L'avantage clé de la composition est la flexibilité. Avec l'héritage, vous êtes limité à une seule classe parente. Avec la composition, vous pouvez combiner plusieurs composants et même les échanger au moment de l'exécution :

class ElectricEngine {
  void start() => print('Electric motor humming');
}

class HybridCar {
  Engine? _gasEngine;
  ElectricEngine? _electricEngine;
  
  HybridCar({bool useElectric = false}) {
    if (useElectric) {
      _electricEngine = ElectricEngine();
    } else {
      _gasEngine = Engine();
    }
  }
}

Une directive courante est « privilégier la composition plutôt que l'héritage ». Utilisez l'héritage lorsqu'il existe une véritable relation « est-un » (is-a) et que vous avez besoin de polymorphisme. Utilisez la composition lorsque vous souhaitez réutiliser un comportement sans couplage fort, ou lorsqu'un objet a besoin de capacités provenant de plusieurs sources.

challenge icon

Défi

Facile

Construisons un système informatique en utilisant la composition ! Au lieu d'essayer de faire hériter un ordinateur de ses composants, vous allez créer des classes de composants distinctes et les composer ensemble - car un ordinateur a un CPU et a de la mémoire, il n'est pas un CPU ou une mémoire.

Vous organiserez votre code en trois fichiers :

  • components.dart : Créez deux classes de composants qui représentent les pièces de l'ordinateur :
    • Une classe CPU avec un String brand et un double speedGHz. Incluez une méthode process() qui retourne une chaîne de caractères : [brand] processing at [speedGHz]GHz
    • Une classe Memory avec un int sizeGB. Incluez une méthode load() qui retourne une chaîne de caractères : Loading [sizeGB]GB of data
  • computer.dart : Créez une classe Computer qui utilise la composition pour combiner les composants. Votre ordinateur doit :
    • Avoir un champ String name
    • Contenir une instance de CPU et une instance de Memory (composition - l'ordinateur POSSÈDE ces pièces)
    • Accepter toutes les valeurs nécessaires via son constructeur pour créer les deux composants en interne
    • Avoir une méthode boot() qui délègue aux deux composants et affiche trois lignes : le nom de l'ordinateur suivi de starting..., puis le résultat de la méthode process du CPU, puis le résultat de la méthode load de la Memory
    • Avoir une méthode specs() qui retourne une chaîne décrivant le système : [name]: [brand] [speedGHz]GHz, [sizeGB]GB RAM
  • main.dart : Importez vos fichiers et démontrez la composition en action :
    • Créez un Computer nommé Workstation avec un CPU Intel cadencé à 3.5 GHz et 16 Go de mémoire
    • Appelez la méthode boot()
    • Affichez le résultat de specs()

Remarquez comment la classe Computer n'étend pas CPU ou Memory - elle les contient et leur délègue le travail. C'est la relation "a-un" (has-a) que la composition crée !

Sortie attendue :

Workstation starting...
Intel processing at 3.5GHz
Loading 16GB of data
Workstation: Intel 3.5GHz, 16GB RAM

Aide-mémoire

La composition crée une relation de type « a un » (has-a) où une classe contient des instances d'autres classes et leur délègue le travail, plutôt que d'en hériter.

Exemple de composition de base :

class Engine {
  void start() => print('Engine started');
}

class Car {
  final Engine _engine = Engine();  // Car HAS an Engine
  
  void start() {
    _engine.start();  // Delegate to the engine
    print('Car is ready to drive');
  }
}

Composition avec flexibilité - échange de composants :

class ElectricEngine {
  void start() => print('Electric motor humming');
}

class HybridCar {
  Engine? _gasEngine;
  ElectricEngine? _electricEngine;
  
  HybridCar({bool useElectric = false}) {
    if (useElectric) {
      _electricEngine = ElectricEngine();
    } else {
      _gasEngine = Engine();
    }
  }
}

Quand utiliser la composition par rapport à l'héritage :

  • Utilisez l'héritage pour les véritables relations de type « est un » (is-a) lorsque vous avez besoin de polymorphisme
  • Utilisez la composition pour réutiliser un comportement sans couplage fort, ou lorsqu'un objet a besoin de capacités provenant de sources multiples
  • Ligne directrice générale : « privilégiez la composition plutôt que l'héritage »

Essayez vous-même

import 'computer.dart';

void main() {
  // TODO : Créer un Computer nommé "Workstation" avec :
  // - CPU Intel cadencé à 3.5 GHz
  // - 16 GB de mémoire

  // TODO : Appeler la méthode boot()

  // TODO : Afficher le résultat de specs()
}
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