Composition vs Héritage
Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 76 sur 104.
Lors de la conception de relations entre les classes, vous avez deux options principales : l'héritage (« est-un ») et la composition (« a-un »). Choisir la bonne approche a un impact significatif sur la flexibilité et la maintenabilité de votre code.
L'héritage crée un couplage fort entre les classes. Une Car qui hérite de Vehicle est liée de manière permanente à cette relation :
class Vehicle {
public:
virtual void start() { std::cout << "Starting vehicle\n"; }
};
class Car : public Vehicle {
public:
void start() override { std::cout << "Starting car\n"; }
};La composition intègre des objets en tant que membres, créant une relation plus flexible. Une Car a un Engine plutôt que d'être un moteur :
class Engine {
public:
void start() { std::cout << "Engine running\n"; }
};
class Car {
private:
Engine engine; // Car A un Engine
public:
void start() { engine.start(); }
};L'avantage clé de la composition est la flexibilité. Vous pouvez facilement échanger des composants, modifier le comportement au moment de l'exécution ou combiner plusieurs capacités sans les contraintes d'une hiérarchie de classes rigide. La conception moderne en C++ privilégie la composition par rapport à l'héritage dans la plupart des cas.
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 des fonctionnalités ou lorsque la relation est mieux décrite par « a un » (has-a) ou « utilise un » (uses-a).
Défi
FacileConstruisons un simulateur de système informatique qui démontre quand utiliser la composition par rapport à l'héritage. Vous allez modéliser un ordinateur qui possède des composants (composition) plutôt que d'être un composant, montrant comment la composition offre la flexibilité de changer des pièces au moment de l'exécution.
Vous organiserez votre code sur trois fichiers :
Components.h: Définissez les composants individuels qu'un ordinateur peut contenir.Créez une classe
CPUavec unstd::string modelet unint coresprivés. Fournissez un constructeur qui prend les deux valeurs, une méthodeprocess()qui afficheCPU [model] processing with [cores] cores, et une méthodegetModel().Créez une classe
Memoryavec unint sizeGBprivé. Fournissez un constructeur, une méthodeload()qui afficheLoading data into [sizeGB]GB RAM, et une méthodegetSize().Créez une classe
Storageavec unstd::string typeprivé (comme "SSD" ou "HDD") et unint capacityGB. Fournissez un constructeur, une méthoderead()qui afficheReading from [capacityGB]GB [type], et des méthodes getter pour les deux champs.Computer.h: Définissez une classeComputerqui utilise la composition pour combiner les composants.Votre
Computerdoit avoir des objets membres privés : unCPU, uneMemory, et unStorage. Utilisez la liste d'initialisation du constructeur pour initialiser ces composants à partir des paramètres.Implémentez ces méthodes :
boot()— afficheBooting computer..., puis appelleload()sur la mémoire,read()sur le stockage, etprocess()sur le processeur, chacun sur une ligne distinctespecs()— affiche les spécifications de l'ordinateur dans ce format :Computer Specs: - CPU: [model] ([cores] cores) - RAM: [size]GB - Storage: [capacity]GB [type]
main.cpp: Lisez six entrées (chacune sur une ligne distincte) :- Nom du modèle de CPU (chaîne de caractères)
- Nombre de cœurs de CPU (entier)
- Taille de la mémoire en Go (entier)
- Type de stockage (chaîne de caractères, par exemple, "SSD")
- Capacité de stockage en Go (entier)
- Une commande : soit "boot" ou "specs"
Créez les objets composants, puis créez un
Computeren utilisant la composition. Selon la commande, appelez soitboot()soitspecs().
Par exemple, avec les entrées Intel i7, 8, 16, SSD, 512, et boot :
Booting computer...
Loading data into 16GB RAM
Reading from 512GB SSD
CPU Intel i7 processing with 8 coresAvec les entrées AMD Ryzen, 6, 32, HDD, 1000, et specs :
Computer Specs:
- CPU: AMD Ryzen (6 cores)
- RAM: 32GB
- Storage: 1000GB HDDRemarquez comment la classe Computer n'hérite d'aucun composant — elle les contient. Cette relation de type "a-un" signifie que vous pourriez facilement créer des ordinateurs avec différentes combinaisons de composants, ou même échanger des composants plus tard si vous ajoutiez des méthodes setter. Cette flexibilité est l'avantage clé de la composition par rapport à l'héritage.
Aide-mémoire
Lors de la conception de relations entre classes, choisissez entre l'héritage (« est un ») et la composition (« a un »).
L'héritage crée un couplage fort entre les classes :
class Vehicle {
public:
virtual void start() { std::cout << "Starting vehicle\n"; }
};
class Car : public Vehicle {
public:
void start() override { std::cout << "Starting car\n"; }
};La composition intègre des objets en tant que membres, créant ainsi des relations flexibles :
class Engine {
public:
void start() { std::cout << "Engine running\n"; }
};
class Car {
private:
Engine engine; // Car A un Engine
public:
void start() { engine.start(); }
};Quand utiliser l'un ou l'autre :
- Utilisez l'héritage pour les véritables relations « est un » lorsque vous avez besoin de polymorphisme
- Utilisez la composition pour réutiliser des fonctionnalités ou pour des relations « a un »/« utilise un »
- La conception moderne en C++ privilégie la composition pour sa flexibilité — vous pouvez échanger des composants, changer le comportement à l'exécution et combiner des capacités sans hiérarchies rigides
Essayez vous-même
#include <iostream>
#include <string>
#include "Computer.h"
using namespace std;
int main() {
// Lire les entrées
string cpuModel;
getline(cin, cpuModel); // Nom du modèle de CPU (peut contenir des espaces)
int cpuCores;
cin >> cpuCores; // Nombre de cœurs du CPU
int memorySize;
cin >> memorySize; // Taille de la mémoire en GB
string storageType;
cin >> storageType; // Type de stockage (ex: "SSD")
int storageCapacity;
cin >> storageCapacity; // Capacité de stockage en GB
string command;
cin >> command; // Commande : "boot" ou "specs"
// TODO: Créer les objets composants (CPU, Memory, Storage)
// TODO: Créer un objet Computer en utilisant la composition
// TODO: Selon la commande, appeler soit boot() soit specs()
return 0;
}
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 externesBuild et Compilation C++Fichiers d'en-tête et Fichiers sourceEspaces de noms et PortéeIntroduction à la POO en C++Classes vs ObjetsLe pointeur 'this'Méthodes (Fonctions membres)Attributs (Membres de données)Bases des Ctors et DtorsRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d'instance vs membres statiquesGetters et SettersFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d'accès à l'héritageOrdre d'appel des Ctor et DtorRedéfinition de méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif - Hiérarchie des employés10Présentation de la STL
Présentation et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - Fréquence des mots13Patrons de conception, Partie 1
Introduction aux patrons de conceptionPatron SingletonFabrique & Fabrique abstraitePatron MonteurPatron ObservateurPatron Stratégie2Gestion de la mémoire
Mémoire Pile vs TasPointeurs et RéférencesMémoire dynamique (new/delete)Smart Pointers en C++RAII en C++Récapitulatif - Gestionnaire de tableaux dynamiques5Encapsulation
Spécificateurs d'accès en C++Spécificateurs d'accès en profondeurMasquage d'informationStruct vs ClassClasses imbriquées et internesRécapitulatif - Système de gestion d'étudiants8Polymorphisme
Polymorphisme : Compilation vs ExécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d'interfaces en C++Dynamic Casting & RTTIRécapitulatif - Calculateur de formes11Concepts avancés de la POO
Composition vs HéritageMixins via CRTPIdiome PimplEffacement de typeEnum Classes & Typage fortGestion des exceptions en POOHiérarchies d'exceptions personnalisées14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeRAII en tant que patron3Constructeurs et Destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d'initialisation du constructeurConstructeurs déléguésAnalyse approfondie du destructeurRègle de trois / cinq / zéroRécapitulatif - Classe String6Surcharge d'opérateurs
Introduction à la surcharge d'opérateursSurcharge des opérateurs arithmétiquesSurcharge des opérateurs de comparaisonOpérateurs de fluxSurcharge de l'opérateur d'affectationSurcharge des opérateurs [] et ()Opérateurs de conversion de typeRécapitulatif - Classe Matrix9Templates
Templates de fonctionsTemplates de classesSpécialisation de templatesTemplates variadiquesBases de SFINAE & Type TraitsRécapitulatif - Conteneur générique