Menu
Coddy logo textTech

Patron Composite

Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 96 sur 107.

Le patron Composite vous permet de traiter les objets individuels et les groupes d'objets de manière uniforme. Alors que State change de comportement en fonction de l'état interne, Composite construit des structures arborescentes où les feuilles et les conteneurs partagent la même interface.

Ce patron est parfait pour les structures hiérarchiques telles que les systèmes de fichiers, les organigrammes ou les composants d'interface utilisateur. En Go, nous définissons une interface commune que les éléments individuels et les conteneurs implémentent :

type Component interface {
    GetSize() int
}

type File struct {
    Name string
    Size int
}

func (f File) GetSize() int {
    return f.Size
}

Le composite (conteneur) contient des enfants et implémente la même interface en agrégeant leurs résultats :

type Folder struct {
    Name     string
    Children []Component
}

func (f *Folder) Add(c Component) {
    f.Children = append(f.Children, c)
}

func (f Folder) GetSize() int {
    total := 0
    for _, child := range f.Children {
        total += child.GetSize()
    }
    return total
}

Désormais, vous pouvez imbriquer des dossiers dans des dossiers, et l'appel à GetSize() fonctionne de manière identique, qu'il s'agisse d'un seul fichier ou d'une arborescence complète de répertoires :

docs := &Folder{Name: "docs"}
docs.Add(File{Name: "readme.txt", Size: 100})
docs.Add(File{Name: "notes.txt", Size: 50})

root := &Folder{Name: "root"}
root.Add(docs)
root.Add(File{Name: "config.json", Size: 25})

fmt.Println(root.GetSize())  // 175

Le pattern Composite est idéal 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 identique.

challenge icon

Défi

Facile

Construisons un système d'organigramme en utilisant le patron de conception 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 — ce qui est parfait pour calculer les salaires totaux dans n'importe quelle partie de l'organisation.

Vous organiserez votre code sur trois fichiers :

  • component.go : Définissez l'interface commune que les individus et les groupes implémenteront.

    Créez une interface OrgComponent avec deux méthodes :

    • GetSalary() int — retourne le salaire total pour ce composant
    • GetName() string — retourne le nom de l'employé ou du département
  • organization.go : Implémentez à la fois les types feuille (employé individuel) et composite (département).

    Créez une structure Employee avec les champs Name (string) et Salary (int). Sa méthode GetSalary() retourne son propre salaire, et GetName() retourne son nom.

    Créez une structure Department avec un champ Name (string) et une tranche (slice) Members qui contient des éléments OrgComponent. Ajoutez une méthode Add(c OrgComponent) pour ajouter des membres. Sa méthode GetSalary() doit retourner la somme des salaires de tous les membres, et GetName() retourne le nom du département.

  • main.go : Construisez une structure organisationnelle et calculez les salaires.

    Lisez le nombre d'employés. Pour chaque employé, lisez son nom et son salaire, en créant des instances de Employee. Ensuite, lisez le nombre de départements. Pour chaque département, lisez son nom et le nombre d'indices de membres, suivis de ces indices (basés sur 0, se référant aux employés ou départements précédemment créés dans l'ordre de création).

    Après avoir construit la structure, lisez un dernier indice et affichez le nom de ce composant et son salaire total au format : [name]: [salary]

Les entrées suivantes seront fournies :

  • Le nombre d'employés, puis le nom et le salaire de chaque employé sur des lignes séparées
  • Le nombre de départements, puis le nom de chaque département, le nombre de membres et les indices des membres
  • L'indice final à interroger

Par exemple, avec :

3
Alice
50000
Bob
60000
Carol
55000
1
Engineering
3
0
1
2
3

Votre sortie devrait être :

Engineering: 165000

Et avec :

4
Alice
50000
Bob
60000
Carol
55000
Dave
70000
2
Dev
2
0
1
QA
2
2
3
4

Votre sortie devrait être :

QA: 125000

Et avec :

2
Alice
50000
Bob
60000
0
1

Votre sortie devrait être :

Bob: 60000

Remarquez comment GetSalary() fonctionne de la même manière, que vous l'appeliez sur un seul employé ou sur un département entier — le patron Composite vous permet de traiter les individus et les groupes uniformément via la même interface !

Aide-mémoire

Le pattern Composite traite les objets individuels et les groupes d'objets de manière uniforme via une interface partagée. Il est idéal pour les structures hiérarchiques comme les systèmes de fichiers, les organigrammes ou les composants d'interface utilisateur.

Définissez une interface commune que les éléments individuels et les conteneurs implémentent :

type Component interface {
    GetSize() int
}

Implémentez la feuille (élément individuel) :

type File struct {
    Name string
    Size int
}

func (f File) GetSize() int {
    return f.Size
}

Implémentez le composite (conteneur) qui contient des enfants et agrège leurs résultats :

type Folder struct {
    Name     string
    Children []Component
}

func (f *Folder) Add(c Component) {
    f.Children = append(f.Children, c)
}

func (f Folder) GetSize() int {
    total := 0
    for _, child := range f.Children {
        total += child.GetSize()
    }
    return total
}

Exemple d'utilisation montrant le traitement uniforme des éléments individuels et des compositions :

docs := &Folder{Name: "docs"}
docs.Add(File{Name: "readme.txt", Size: 100})
docs.Add(File{Name: "notes.txt", Size: 50})

root := &Folder{Name: "root"}
root.Add(docs)
root.Add(File{Name: "config.json", Size: 25})

fmt.Println(root.GetSize())  // 175

Essayez vous-même

package main

import "fmt"

func main() {
	// Lire le nombre d'employés
	var numEmployees int
	fmt.Scanln(&numEmployees)

	// Stocker tous les composants (employés et départements) dans l'ordre de création
	var components []OrgComponent

	// TODO: Lire le nom et le salaire de chaque employé
	// Créer des instances d'Employee et les ajouter à la tranche components
	for i := 0; i < numEmployees; i++ {
		var name string
		var salary int
		fmt.Scanln(&name)
		fmt.Scanln(&salary)
		// TODO: Créer l'employé et l'ajouter aux composants
	}

	// Lire le nombre de départements
	var numDepartments int
	fmt.Scanln(&numDepartments)

	// TODO: Lire le nom, le nombre de membres et les indices des membres de chaque département
	// Créer des instances de Department, ajouter les membres par indice, et ajouter aux composants
	for i := 0; i < numDepartments; i++ {
		var deptName string
		var memberCount int
		fmt.Scanln(&deptName)
		fmt.Scanln(&memberCount)
		// TODO: Créer le département, lire les indices des membres, ajouter les membres, ajouter aux composants
	}

	// Lire l'indice final à interroger
	var queryIndex int
	fmt.Scanln(&queryIndex)

	// TODO: Afficher le nom du composant et le salaire total au format : [name]: [salary]
	// Exemple : fmt.Printf("%s: %d\n", name, salary)
}
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