Menu
Coddy logo textTech

Patron Composite

Fait partie de la section Programmation Orientée Objet du Journey Java de Coddy — leçon 75 sur 87.

Le patron de conception Composite est un patron de conception structurel qui vous permet de composer des objets en structures arborescentes pour représenter des hiérarchies partie-tout. Il permet aux clients de traiter les objets individuels et les compositions d'objets de manière uniforme via une interface commune.

Pensez à un système de fichiers : un dossier peut contenir des fichiers et d'autres dossiers, qui peuvent eux-mêmes contenir d'autres fichiers et dossiers. Que vous manipuliez un seul fichier ou une structure de dossiers entière, vous voulez effectuer des opérations comme « get size » de la même manière. Le patron définit une interface Component, des objets Leaf (éléments individuels) et des objets Composite (conteneurs qui contiennent des enfants) :

interface FileComponent {
    void display(String indent);
    int getSize();
}

class File implements FileComponent {
    private String name;
    private int size;
    
    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }
    
    public void display(String indent) {
        System.out.println(indent + name + " (" + size + "KB)");
    }
    
    public int getSize() {
        return size;
    }
}

class Folder implements FileComponent {
    private String name;
    private List<FileComponent> children = new ArrayList<>();
    
    public Folder(String name) {
        this.name = name;
    }
    
    public void add(FileComponent component) {
        children.add(component);
    }
    
    public void display(String indent) {
        System.out.println(indent + name + "/");
        for (FileComponent child : children) {
            child.display(indent + "  ");
        }
    }
    
    public int getSize() {
        return children.stream().mapToInt(FileComponent::getSize).sum();
    }
}

Le composite délègue les opérations à ses enfants, permettant des structures récursives. Les clients interagissent avec l'arbre sans savoir s'ils travaillent avec une feuille ou un composite :

Folder root = new Folder("Documents");
root.add(new File("resume.pdf", 150));

Folder photos = new Folder("Photos");
photos.add(new File("vacation.jpg", 2000));
root.add(photos);

root.display("");
System.out.println("Total: " + root.getSize() + "KB");

Le pattern Composite est idéal pour représenter des hiérarchies telles que des organigrammes, des composants d'interface utilisateur ou des systèmes de menus, où vous devez traiter les groupes et les individus de manière identique.

challenge icon

Défi

Facile

Construisons un système d'organigramme en utilisant le Composite Pattern ! Vous allez créer une structure où les départements peuvent contenir des employés et d'autres sous-départements, vous permettant de calculer les salaires totaux et d'afficher la hiérarchie de manière uniforme, que vous regardiez un seul employé ou une division entière.

Vous organiserez votre code sur quatre fichiers :

  • OrganizationComponent.java : Définissez l'interface du composant que les employés et les départements implémenteront. Elle doit déclarer deux méthodes : showDetails(String indent) pour afficher le composant avec l'indentation appropriée, et getSalary() qui retourne le salaire total sous forme d'entier.
  • Employee.java : Créez la classe "feuille" (leaf) représentant les travailleurs individuels. Un Employee a un nom (String) et un salaire (int), tous deux définis via le constructeur. Lorsque showDetails est appelée, elle doit afficher [indent][name]: $[salary]. La méthode getSalary retourne simplement le salaire de l'employé.
  • Department.java : Créez la classe composite qui peut contenir à la fois des employés et des sous-départements. Un Department a un nom (String) et maintient une liste d'enfants de type OrganizationComponent. Il doit avoir une méthode add(OrganizationComponent component) pour ajouter des membres. Lorsque showDetails est appelée, elle affiche [indent][name] Department, puis appelle showDetails sur chaque enfant avec une indentation augmentée (ajoutez deux espaces). La méthode getSalary retourne la somme des salaires de tous les enfants.
  • Main.java : Construisez votre organisation ! Vous recevrez quatre entrées : deux noms d'employés avec leurs salaires, et un nom de sous-département avec un employé.

    Créez un département "Engineering" comme racine. Ajoutez-y deux employés en utilisant les deux premières paires nom/salaire. Ensuite, créez un sous-département en utilisant la troisième entrée, ajoutez-y un employé en utilisant la quatrième paire nom/salaire, et ajoutez ce sous-département à Engineering.

    Appelez showDetails("") sur le département Engineering, puis affichez le salaire total au format Total Salary: $[amount].

Vous recevrez les entrées dans cet ordre : nom de l'employé1 (String), salaire de l'employé1 (int), nom de l'employé2 (String), salaire de l'employé2 (int), nom du sous-département (String), nom de l'employé3 (String), salaire de l'employé3 (int).

Par exemple, avec les entrées Alice, 75000, Bob, 65000, QA, Charlie, 55000, votre sortie serait :

Engineering Department
  Alice: $75000
  Bob: $65000
  QA Department
    Charlie: $55000
Total Salary: $195000

Remarquez comment les mêmes méthodes showDetails et getSalary fonctionnent de manière transparente, qu'elles soient appelées sur un seul employé ou sur un département entier contenant des sous-départements imbriqués. La structure composite gère la récursion automatiquement, vous permettant de traiter toute la hiérarchie de manière uniforme !

Aide-mémoire

Le Composite Pattern (ou patron de conception Composite) est un patron de conception structurel qui compose des objets dans des structures arborescentes pour représenter des hiérarchies partie-tout. Il permet de traiter les objets individuels et les compositions de manière uniforme via une interface commune.

Le patron se compose de trois éléments clés :

  • Component : Interface définissant les opérations communes
  • Leaf : Objets individuels qui implémentent l'interface du composant
  • Composite : Objets conteneurs qui détiennent des enfants et leur délèguent des opérations

Exemple d'implémentation d'un système de fichiers :

// Interface Component
interface FileComponent {
    void display(String indent);
    int getSize();
}

// Classe Leaf
class File implements FileComponent {
    private String name;
    private int size;
    
    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }
    
    public void display(String indent) {
        System.out.println(indent + name + " (" + size + "KB)");
    }
    
    public int getSize() {
        return size;
    }
}

// Classe Composite
class Folder implements FileComponent {
    private String name;
    private List<FileComponent> children = new ArrayList<>();
    
    public Folder(String name) {
        this.name = name;
    }
    
    public void add(FileComponent component) {
        children.add(component);
    }
    
    public void display(String indent) {
        System.out.println(indent + name + "/");
        for (FileComponent child : children) {
            child.display(indent + "  ");
        }
    }
    
    public int getSize() {
        return children.stream().mapToInt(FileComponent::getSize).sum();
    }
}

Utilisation de la structure composite :

Folder root = new Folder("Documents");
root.add(new File("resume.pdf", 150));

Folder photos = new Folder("Photos");
photos.add(new File("vacation.jpg", 2000));
root.add(photos);

root.display("");
System.out.println("Total: " + root.getSize() + "KB");

Le composite délègue les opérations à ses enfants de manière récursive. Les clients interagissent avec l'arborescence sans savoir s'ils travaillent avec une feuille ou un composite.

Cas d'utilisation : Organigrammes, composants d'interface utilisateur, systèmes de menus, systèmes de fichiers — toute hiérarchie où les groupes et les individus nécessitent un traitement identique.

Essayez vous-même

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // Lire les entrées
        String emp1Name = scanner.nextLine();
        int emp1Salary = Integer.parseInt(scanner.nextLine());
        String emp2Name = scanner.nextLine();
        int emp2Salary = Integer.parseInt(scanner.nextLine());
        String subDeptName = scanner.nextLine();
        String emp3Name = scanner.nextLine();
        int emp3Salary = Integer.parseInt(scanner.nextLine());
        
        // TODO: Créer le département "Engineering" comme racine
        
        // TODO: Créer et ajouter deux employés à Engineering en utilisant les données de emp1 et emp2
        
        // TODO: Créer un sous-département en utilisant subDeptName
        
        // TODO: Créer et ajouter un employé au sous-département en utilisant les données de emp3
        
        // TODO: Ajouter le sous-département à Engineering
        
        // TODO: Appeler showDetails("") sur le département Engineering
        
        // TODO: Afficher le salaire total au format : Total Salary: $[amount]
    }
}
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