Patron Composite
Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 101 sur 104.
Le patron Composite vous permet de traiter les objets individuels et les groupes d'objets de manière uniforme. Il compose les objets en structures arborescentes où les éléments simples et les conteneurs d'éléments partagent la même interface. C'est idéal pour représenter des hiérarchies telles que des systèmes de fichiers, des organigrammes ou des composants d'interface utilisateur.
Le patron comporte trois parties clés : une interface Component qui définit les opérations communes, des classes Leaf représentant des objets individuels, et des classes Composite qui contiennent des enfants et leur délèguent les opérations :
#include <iostream>
#include <memory>
#include <vector>
#include <string>
// Interface du composant (Component interface)
class FileSystemItem {
public:
virtual void display(int indent = 0) const = 0;
virtual int getSize() const = 0;
virtual ~FileSystemItem() = default;
};
// Feuille - représente des fichiers individuels (Leaf - represents individual files)
class File : public FileSystemItem {
std::string name;
int size;
public:
File(const std::string& n, int s) : name(n), size(s) {}
void display(int indent = 0) const override {
std::cout << std::string(indent, ' ') << name
<< " (" << size << " KB)\n";
}
int getSize() const override { return size; }
};
// Composite - contient d'autres composants (Composite - contains other components)
class Folder : public FileSystemItem {
std::string name;
std::vector<std::shared_ptr<FileSystemItem>> children;
public:
Folder(const std::string& n) : name(n) {}
void add(std::shared_ptr<FileSystemItem> item) {
children.push_back(item);
}
void display(int indent = 0) const override {
std::cout << std::string(indent, ' ') << "[" << name << "]\n";
for (const auto& child : children) {
child->display(indent + 2);
}
}
int getSize() const override {
int total = 0;
for (const auto& child : children) {
total += child->getSize();
}
return total;
}
};Le composite Folder stocke des enfants et implémente les opérations en itérant à travers eux. Lorsque vous appelez getSize() sur un dossier, il calcule récursivement la taille totale de tous les éléments contenus. Le code client n'a pas besoin de savoir s'il travaille avec un fichier ou un dossier — les deux répondent à la même interface.
Utilisez le pattern Composite lorsque vous devez représenter des hiérarchies partie-tout et que vous souhaitez que les clients traitent les objets individuels et les compositions de manière uniforme.
Défi
FacileConstruisons un système d'Organigramme en utilisant le patron de conception Composite. Vous allez créer une hiérarchie où les employés individuels et les départements (qui contiennent d'autres employés ou sous-départements) peuvent être traités de manière uniforme. Cela reflète la structure des entreprises réelles : les départements contiennent des personnes et d'autres départements, formant une structure en arbre.
Vous organiserez votre code sur trois fichiers :
OrgComponent.h: Définissez l'interface du composant que les employés et les départements implémenteront.Créez une classe abstraite
OrgComponentavec :getName()— retourne le nom du composantgetSalary()— retourne le salaire total (pour les employés, leur propre salaire ; pour les départements, la somme de tous les salaires contenus)display(int indent = 0)— affiche le composant avec l'indentation appropriée
Incluez un destructeur virtuel.
Organization.h: Implémentez les classes feuille (leaf) et composite.Créez une classe
Employee(la feuille) qui stocke un nom et un salaire. Sa méthodedisplay()doit afficher les informations de l'employé dans ce format :[indent spaces]- [name] ($[salary])Créez une classe
Department(le composite) qui stocke un nom et une collection d'enfantsOrgComponenten utilisantstd::shared_ptr. Implémentez :add(std::shared_ptr<OrgComponent> component)— ajoute un enfant au départementgetSalary()— calcule récursivement le salaire total de tous les membresdisplay()— affiche le nom du département entre crochets, puis affiche tous les enfants avec une indentation augmentée (ajoutez 2 espaces par niveau)
Le format d'affichage du département doit être :
[indent spaces][Department Name] [children displayed with indent + 2]main.cpp: Construisez et affichez une structure organisationnelle.Lisez quatre entrées :
- Nom de l'entreprise (chaîne de caractères)
- Nom du département (chaîne de caractères)
- Nom et salaire du premier employé (format :
name,salary) - Nom et salaire du deuxième employé (format :
name,salary)
Construisez cette structure : Créez une entreprise (département de niveau supérieur), ajoutez-y un sous-département et ajoutez les deux employés à ce sous-département. Ensuite, affichez l'organisation complète et imprimez le salaire total de l'entreprise.
Après avoir affiché la structure, imprimez :
Total Salary: $[amount]
Par exemple, avec les entrées TechCorp, Engineering, Alice,75000, et Bob,65000 :
[TechCorp]
[Engineering]
- Alice ($75000)
- Bob ($65000)
Total Salary: $140000Avec les entrées StartupInc, Development, Carol,80000, et Dave,70000 :
[StartupInc]
[Development]
- Carol ($80000)
- Dave ($70000)
Total Salary: $150000Remarquez comment getSalary() fonctionne de la même manière, qu'il soit appelé sur un employé ou sur un département — le département agrège automatiquement les salaires de tous ses membres. Le code client n'a pas besoin de faire la distinction entre les employés individuels et les départements entiers lors du calcul des totaux ou de l'affichage de la hiérarchie.
Aide-mémoire
Le patron Composite traite les objets individuels et les groupes d'objets de manière uniforme en les composant dans des structures arborescentes où les éléments simples et les conteneurs partagent la même interface.
Le patron comporte trois parties clés :
- Composant : Une interface définissant les opérations communes
- Feuille : Classes représentant des objets individuels
- Composite : Classes qui contiennent des enfants et leur délèguent les opérations
Exemple d'implémentation d'un système de fichiers :
#include <iostream>
#include <memory>
#include <vector>
#include <string>
// Interface Composant
class FileSystemItem {
public:
virtual void display(int indent = 0) const = 0;
virtual int getSize() const = 0;
virtual ~FileSystemItem() = default;
};
// Feuille - représente des fichiers individuels
class File : public FileSystemItem {
std::string name;
int size;
public:
File(const std::string& n, int s) : name(n), size(s) {}
void display(int indent = 0) const override {
std::cout << std::string(indent, ' ') << name
<< " (" << size << " KB)\n";
}
int getSize() const override { return size; }
};
// Composite - contient d'autres composants
class Folder : public FileSystemItem {
std::string name;
std::vector<std::shared_ptr<FileSystemItem>> children;
public:
Folder(const std::string& n) : name(n) {}
void add(std::shared_ptr<FileSystemItem> item) {
children.push_back(item);
}
void display(int indent = 0) const override {
std::cout << std::string(indent, ' ') << "[" << name << "]\n";
for (const auto& child : children) {
child->display(indent + 2);
}
}
int getSize() const override {
int total = 0;
for (const auto& child : children) {
total += child->getSize();
}
return total;
}
};Le composite stocke les enfants en utilisant std::shared_ptr et implémente les opérations en itérant à travers eux. Les opérations comme getSize() fonctionnent de manière récursive, calculant les totaux sur l'ensemble de la hiérarchie.
Utilisez le Composite lorsque vous devez représenter des hiérarchies partie-tout et que vous souhaitez que les clients traitent les objets individuels et les compositions de manière uniforme.
Essayez vous-même
#include <iostream>
#include <string>
#include <memory>
#include <sstream>
#include "Organization.h"
int main() {
// Lire les entrées
std::string companyName;
std::string departmentName;
std::string employee1Input;
std::string employee2Input;
std::getline(std::cin, companyName);
std::getline(std::cin, departmentName);
std::getline(std::cin, employee1Input);
std::getline(std::cin, employee2Input);
// Lambda auxiliaire pour analyser le format "name,salary"
auto parseEmployee = [](const std::string& input) -> std::pair<std::string, int> {
size_t commaPos = input.find(',');
std::string name = input.substr(0, commaPos);
int salary = std::stoi(input.substr(commaPos + 1));
return {name, salary};
};
auto [name1, salary1] = parseEmployee(employee1Input);
auto [name2, salary2] = parseEmployee(employee2Input);
// TODO: Créer l'entreprise en tant que Department de niveau supérieur
// TODO: Créer un sous-département
// TODO: Créer deux objets Employee en utilisant les données analysées
// TODO: Ajouter les employés au sous-département
// TODO: Ajouter le sous-département à l'entreprise
// TODO: Afficher toute la structure de l'organisation
// TODO: Afficher le salaire total au format : Total Salary: $[amount]
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