Menu
Coddy logo textTech

Patron Composite

Fait partie de la section Programmation Orientée Objet du Journey C# de Coddy — leçon 61 sur 70.

Le pattern Composite est un patron de conception structurel qui vous permet de composer des objets dans des structures arborescentes et de traiter les objets individuels et les groupes d'objets de manière uniforme. Il est parfait pour représenter des hiérarchies partie-tout où vous souhaitez travailler à la fois avec des éléments uniques et des collections via la même interface.

Le patron comporte trois composants clés : une interface de composant qui définit les opérations communes, des classes feuilles représentant des objets individuels, et des classes composites qui contiennent des enfants et implémentent la même interface :

public interface IFileSystemItem
{
    string Name { get; }
    int GetSize();
}

public class File : IFileSystemItem
{
    public string Name { get; }
    private int _size;
    
    public File(string name, int size)
    {
        Name = name;
        _size = size;
    }
    
    public int GetSize() => _size;
}

public class Folder : IFileSystemItem
{
    public string Name { get; }
    private List<IFileSystemItem> _items = new List<IFileSystemItem>();
    
    public Folder(string name) => Name = name;
    
    public void Add(IFileSystemItem item) => _items.Add(item);
    
    public int GetSize() => _items.Sum(item => item.GetSize());
}

La beauté de ce patron est que les clients n'ont pas besoin de savoir s'ils travaillent avec un seul fichier ou une hiérarchie complète de dossiers :

var root = new Folder("root");
root.Add(new File("doc.txt", 100));

var subFolder = new Folder("images");
subFolder.Add(new File("photo.jpg", 500));
subFolder.Add(new File("icon.png", 50));

root.Add(subFolder);

Console.WriteLine(root.GetSize());  // 650

Le patron Composite est idéal pour les systèmes de fichiers, les arbres de composants d'interface utilisateur, les organigrammes ou toute structure où les conteneurs et les contenus doivent être traités de la même manière. Il simplifie le code client en éliminant le besoin de distinguer les éléments simples des éléments complexes.

challenge icon

Défi

Facile

Construisons un système d'organigramme en utilisant le motif Composite. Vous allez créer une structure où les employés individuels et les départements (qui contiennent des employés ou d'autres départements) peuvent être traités de manière uniforme, vous permettant de calculer le coût salarial total à n'importe quel niveau de la hiérarchie.

Vous organiserez votre code sur trois fichiers :

  • OrgComponent.cs : Définissez une interface IOrgComponent dans l'espace de noms Organization avec deux membres : une propriété Name (chaîne de caractères) et une méthode GetSalary() qui retourne un entier. Cette interface représente le contrat commun que les employés individuels et les départements suivront.
  • OrgClasses.cs : Créez deux classes dans le même espace de noms :
    • Employee - une classe feuille représentant un travailleur individuel. Elle prend un nom et un salaire dans son constructeur et implémente l'interface en retournant son propre salaire.
    • Department - une classe composite représentant un groupe. Elle prend un nom dans son constructeur et maintient une liste d'éléments IOrgComponent. Incluez une méthode Add(IOrgComponent component) pour ajouter des membres. Sa méthode GetSalary() doit retourner la somme des salaires de tous ses membres.
  • Program.cs : Construisez une structure organisationnelle basée sur l'entrée et calculez le salaire total. Vous créerez des départements et des employés, les imbriquerez de manière appropriée, et démontrerez comment le motif composite vous permet de traiter toute la hiérarchie de manière uniforme.

Vous recevrez les entrées suivantes :

  • Le nombre de composants à créer
  • Pour chaque composant : le type (employee ou department), suivi du nom, et pour les employés, leur salaire. Pour les départements, vous recevrez également le nom du département parent (ou root s'il s'agit d'un département de premier niveau)

Format d'entrée pour chaque composant :

employee
{name}
{salary}
{parent_department}

department
{name}
{parent_department}

Après avoir construit la structure, affichez le salaire total du département racine avec le format {root_name} Total Salary: {amount}.

Par exemple, si les entrées sont :

5
department
Engineering
root
employee
Alice
5000
Engineering
employee
Bob
4500
Engineering
department
QA
Engineering
employee
Charlie
4000
QA

La sortie devrait être :

Engineering Total Salary: 13500

Le département Engineering contient Alice (5000), Bob (4500), et le département QA. QA contient Charlie (4000). Lorsque vous appelez GetSalary() sur Engineering, il calcule récursivement le total de tous les composants imbriqués - démontrant ainsi comment le motif Composite vous permet de travailler avec des structures arborescentes complexes via une interface simple et uniforme !

Aide-mémoire

Le pattern Composite est un patron de structure qui compose des objets dans des structures en arbre et traite les objets individuels et les groupes de manière uniforme via la même interface. Il est idéal pour les hiérarchies partie-tout.

Le pattern comporte trois composants clés :

  • Interface composant : Définit les opérations communes
  • Classes feuilles : Représentent les objets individuels
  • Classes composites : Contiennent des enfants et implémentent la même interface

Exemple d'implémentation de base :

// Interface composant
public interface IFileSystemItem
{
    string Name { get; }
    int GetSize();
}

// Classe feuille
public class File : IFileSystemItem
{
    public string Name { get; }
    private int _size;
    
    public File(string name, int size)
    {
        Name = name;
        _size = size;
    }
    
    public int GetSize() => _size;
}

// Classe composite
public class Folder : IFileSystemItem
{
    public string Name { get; }
    private List<IFileSystemItem> _items = new List<IFileSystemItem>();
    
    public Folder(string name) => Name = name;
    
    public void Add(IFileSystemItem item) => _items.Add(item);
    
    public int GetSize() => _items.Sum(item => item.GetSize());
}

Utilisation - les clients travaillent avec des éléments uniques et des collections de manière uniforme :

var root = new Folder("root");
root.Add(new File("doc.txt", 100));

var subFolder = new Folder("images");
subFolder.Add(new File("photo.jpg", 500));
subFolder.Add(new File("icon.png", 50));

root.Add(subFolder);

Console.WriteLine(root.GetSize());  // 650

Le pattern Composite est utile pour les systèmes de fichiers, les arbres de composants d'interface utilisateur, les organigrammes ou toute structure où les conteneurs et les contenus doivent être traités de la même manière.

Essayez vous-même

using System;
using System.Collections.Generic;
using Organization;

class Program
{
    public static void Main(string[] args)
    {
        int n = Convert.ToInt32(Console.ReadLine());
        
        // TODO : Créer un dictionnaire pour stocker les départements par nom
        // Cela vous aidera à trouver les départements parents lors de l'ajout de composants
        Dictionary<string, Department> departments = new Dictionary<string, Department>();
        
        // TODO : Garder une trace du département racine
        Department root = null;
        
        for (int i = 0; i < n; i++)
        {
            string type = Console.ReadLine();
            
            if (type == "department")
            {
                string name = Console.ReadLine();
                string parent = Console.ReadLine();
                
                // TODO : Créer le département
                // TODO : L'ajouter au dictionnaire departments
                // TODO : Si parent est "root", c'est le département racine
                // TODO : Sinon, trouver le département parent et lui ajouter ce département
            }
            else if (type == "employee")
            {
                string name = Console.ReadLine();
                int salary = Convert.ToInt32(Console.ReadLine());
                string parent = Console.ReadLine();
                
                // TODO : Créer l'employé
                // TODO : Trouver le département parent et lui ajouter l'employé
            }
        }
        
        // TODO : Afficher le salaire total du département racine
        // Format : "{root_name} Total Salary: {amount}"
        Console.WriteLine($"{root.Name} Total Salary: {root.GetSalary()}");
    }
}
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