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()) // 175Le 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.
Défi
FacileConstruisons 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
OrgComponentavec deux méthodes :GetSalary() int— retourne le salaire total pour ce composantGetName() 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
Employeeavec les champsName(string) etSalary(int). Sa méthodeGetSalary()retourne son propre salaire, etGetName()retourne son nom.Créez une structure
Departmentavec un champName(string) et une tranche (slice)Membersqui contient des élémentsOrgComponent. Ajoutez une méthodeAdd(c OrgComponent)pour ajouter des membres. Sa méthodeGetSalary()doit retourner la somme des salaires de tous les membres, etGetName()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
3Votre sortie devrait être :
Engineering: 165000Et avec :
4
Alice
50000
Bob
60000
Carol
55000
Dave
70000
2
Dev
2
0
1
QA
2
2
3
4Votre sortie devrait être :
QA: 125000Et avec :
2
Alice
50000
Bob
60000
0
1Votre sortie devrait être :
Bob: 60000Remarquez 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()) // 175Essayez 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)
}
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 en Go
Fichiers externesEspace de travail et Modules GoPackages et ImportsNoms exportés vs non exportésIntroduction à la POO en GoLes Structs comme ClassesDéfinir des méthodes sur les StructsReceveurs par pointeur vs par valeurInitialisation de StructsFonctions constructeursRécapitulatif - Calculatrice simple4Interfaces
Introduction aux interfacesImplémentation impliciteL'interface comme contratInterface vide (any)Assertion de typeType SwitchComposition d'interfacesInterfaces Stringer & ErrorRécapitulatif - Calculateur de formes7Encapsulation
Champs exportés vs non exportésEncapsulation au niveau du packageMéthodes Getter & SetterMasquage d'informations en GoRécapitulatif - Dossiers étudiants10Génériques (Go 1.18+)
Introduction aux génériquesParamètres de typeContraintes de typeStructures génériquesContournement pour les méthodes génériquesRécapitulatif - Collections génériques13Patrons de conception, Partie 1
Introduction aux Design PatternsPatron SingletonPatron FactoryPatron Abstract FactoryPatron ObserverPatron Strategy2Exploration approfondie des Types et Structs
Types de base et compositesDéfinitions de types personnalisésTags de StructStructs anonymesStructs imbriquéesValeurs par défaut et Zero ValuesRécapitulatif - Carnet de contacts5Composition plutôt qu'héritage
Pourquoi Go n'a pas d'héritageLes bases de l'imbrication de structsPromotion de méthodesImbriquer plusieurs structsImbrication vs AgrégationMasquage de méthodes imbriquéesRécapitulatif - Hiérarchie des employés8Gestion des erreurs & POO
L'interface errorTypes d'erreurs personnalisésError Wrapping (fmt.Errorf)Erreurs sentinelleserrors.Is() et errors.As()Panic, Defer et RecoverRécapitulatif - File Parser11Bibliothèque standard et POO
io.Reader & io.WriterL'interface sort.InterfaceL'interface fmt.Stringerencoding/json avec les StructsL'interface http.HandlerRécapitulatif - Modèles d'API REST14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeMiddleware en tant que Décorateur3Pointeurs et Mémoire
Bases des pointeurs en GoPointeurs vers des structsPassage par valeur vs référenceLa fonction new()Garbage Collection en GoRécapitulatif - Constructeur de liste chaînée6Le polymorphisme en Go
Le polymorphisme via les interfacesLe Duck Typing en GoRègles de satisfaction des interfacesCollections polymorphesInjection de dépendancesRécapitulatif - Processeur de paiement9Concurrence et POO
Bases des GoroutinesChannels et CommunicationChannels avec buffer vs sans bufferL'instruction Selectsync.Mutex et sync.RWMutexsync.WaitGroupConception de structures Thread-SafeRécapitulatif - Worker Pool