Composition ou 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 des relations entre les classes, vous avez deux options principales : l’héritage (« is-a ») et la composition (« has-a »). Choisir la bonne approche a un impact considérable sur la flexibilité et la maintenabilité de votre code.
L’héritage crée un couplage étroit entre les classes. Une Car qui hérite de Vehicle est définitivement liée à 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 ainsi une relation plus flexible. Une Car possède 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(); }
};Le principal avantage de la composition est la flexibilité. Vous pouvez facilement remplacer des composants, modifier le comportement lors de l’exécution ou combiner plusieurs fonctionnalités sans les contraintes d’une hiérarchie de classes rigide. La conception moderne en C++ privilégie la composition à l’héritage dans la plupart des cas.
Utilise l’héritage lorsqu’il existe une véritable relation « is-a » et que tu as besoin de polymorphisme. Utilise la composition lorsque tu veux réutiliser des fonctionnalités ou lorsque la relation est mieux décrite comme « has-a » ou « uses-a ».
Défi
FacileConstruisons un simulateur de système informatique qui montre quand utiliser la composition plutôt que l’héritage. Vous modéliserez un ordinateur qui a des composants (composition) plutôt que d’être un composant, afin de montrer comment la composition offre la flexibilité de remplacer les éléments 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 modelprivé et unint coresprivé. Fournissez un constructeur qui accepte ces deux valeurs, une méthodeprocess()qui afficheCPU [model] processing with [cores] cores, ainsi qu’une méthodegetModel().Créez une classe
Memoryavec unint sizeGBprivé. Fournissez un constructeur, une méthodeload()qui afficheLoading data into [sizeGB]GB RAM, ainsi qu’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], ainsi que des méthodes d’accès pour les deux champs.Computer.h: définissez une classeComputerqui utilise la composition pour combiner les composants.Votre
Computerdoit posséder comme membres privés des objets : unCPU, uneMemoryet 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 CPU, chacun sur une ligne distinctespecs(): affiche les spécifications de l’ordinateur au format suivant :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 du CPU (chaîne de caractères)
- Nombre de cœurs du CPU (entier)
- Taille de la mémoire en Go (entier)
- Type de stockage (chaîne de caractères, par exemple "SSD")
- Capacité du stockage en Go (entier)
- Une commande : soit "boot", soit "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 que la classe Computer n’hérite d’aucun composant. Elle les contient. Cette relation « a-un » signifie que vous pourriez facilement créer des ordinateurs avec différentes combinaisons de composants, ou même remplacer les composants ultérieurement si vous ajoutiez des méthodes d’affectation. Cette flexibilité constitue le principal avantage de la composition par rapport à l’héritage.
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 CPU
int memorySize;
cin >> memorySize; // Taille de la mémoire en Go
string storageType;
cin >> storageType; // Type de stockage (p. ex., "SSD")
int storageCapacity;
cin >> storageCapacity; // Capacité de stockage en Go
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 externesGénération et compilation en C++Fichiers d’en-tête et fichiers sourceEspaces de noms et portéeIntroduction à la POO en C++Classes et objetsLe pointeur « this »Méthodes (fonctions membres)Attributs (membres de données)Bases des constructeurs et destructeursRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d’instance et statiquesAccesseurs et mutateursFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d’accès de l’héritageOrdre d’appel des constructeurs et destructeursRedéfinition des méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif : hiérarchie des employés10Vue d’ensemble de la STL
Vue d’ensemble et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - fréquence des mots13Modèles de conception, partie 1
Introduction aux modèles de conceptionModèle SingletonFabrique et fabrique abstraiteModèle BuilderModèle ObserverModèle Strategy2Gestion de la mémoire
Mémoire de pile ou de tasPointeurs et référencesMémoire dynamique (new/delete)Pointeurs intelligents 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 de l’informationStruct vs classeClasses imbriquées et internesRécapitulatif - Système de gestion des dossiers étudiants8Polymorphisme
Polymorphisme à la compilation vs à l’exécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d’interfaces en C++Conversion dynamique et RTTIRécapitulatif - Calculateur de formes11Concepts avancés de la POO
Composition ou héritageMixins via CRTPIdiom PimplEffacement de typeClasses enum et typage fortGestion des exceptions en POOHiérarchies d’exceptions personnalisées3Constructeurs et destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d’initialisation des constructeursConstructeurs déléguésApprofondissement des destructeursRègle des trois / cinq / zéroRécapitulatif - Classe String6Surcharge des opérateurs
Introduction à la surcharge des 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 des templatesTemplates variadiquesBases de SFINAE et des traits de typesRécapitulatif - Conteneur génériqueEntraînez-vous par vous-même : Compilateur C++ en ligne