Pattern Composite
Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy. Leçon 101 sur 104.
Le pattern Composite vous permet de traiter uniformément les objets individuels et les groupes d’objets. Il compose les objets en structures arborescentes où les éléments individuels et les conteneurs d’éléments partagent la même interface. C’est idéal pour représenter des hiérarchies telles que les systèmes de fichiers, les organigrammes ou les composants d’interface utilisateur.
Le modèle comporte trois éléments 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
class FileSystemItem {
public:
virtual void display(int indent = 0) const = 0;
virtual int getSize() const = 0;
virtual ~FileSystemItem() = default;
};
// Feuille - représente les 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 Folder stocke ses enfants et implémente les opérations en les parcourant. Lorsque vous appelez getSize() sur un dossier, il calcule récursivement la taille totale de tous les éléments qu’il contient. 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 Composite lorsque vous devez représenter des hiérarchies partie-tout et que vous souhaitez que les clients traitent uniformément les objets individuels et les compositions.
Défi
FacileConstruisons un système d’organigramme d’entreprise utilisant le modèle Composite. Vous allez créer une hiérarchie dans laquelle les employés individuels et les départements (qui contiennent d’autres employés ou sous-départements) peuvent être traités uniformément. Cela reflète la manière dont les entreprises réelles sont structurées : les départements contiennent des personnes et d’autres départements, formant une structure arborescente.
Vous allez organiser votre code sur trois fichiers :
OrgComponent.h: définir l’interface du composant que les employés et les départements implémenteront tous deux.Créez une classe abstraite
OrgComponentavec :getName(): renvoie le nom du composantgetSalary(): renvoie 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 une indentation appropriée
Incluez un destructeur virtuel.
Organization.h: implémenter les classes feuille 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’enfantsOrgComponentà l’aide destd::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 accrue (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: construire et afficher 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 second employé (format :
name,salary)
Construisez cette structure : créez une entreprise (département de niveau supérieur), ajoutez-y un sous-département, puis ajoutez les deux employés à ce sous-département. Affichez ensuite l’ensemble de l’organisation et affichez le salaire total de l’entreprise.
Après avoir affiché la structure, affichez :
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 uniformément, 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 des employés individuels et des départements entiers lors du calcul des totaux ou de l’affichage de la hiérarchie.
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 d'aide 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 premier niveau
// TODO: Créer un sous-département
// TODO: Créer deux objets Employee à l'aide des données analysées
// TODO: Ajouter les employés au sous-département
// TODO: Ajouter le sous-département à l'entreprise
// TODO: Afficher l'ensemble de 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 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ées14Design Patterns – Partie 2
Pattern CommandePattern AdaptateurPattern DécorateurPattern Méthode ModèlePattern ÉtatPattern CompositeRAII comme Pattern3Constructeurs 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