Imbrication vs Agrégation
Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 38 sur 107.
Vous avez appris que l'incorporation (embedding) promeut les champs et les méthodes vers la struct externe. Mais Go prend également en charge l'agrégation, où vous incluez une autre struct en tant que champ nommé. Comprendre quand utiliser chaque approche est essentiel pour une bonne conception.
Avec l'imbrication (embedding), vous omettez le nom du champ, et les membres du type interne deviennent directement accessibles :
type Writer struct{}
func (w Writer) Write() string { return "writing" }
type Document struct {
Writer // imbrication - pas de nom de champ
}
d := Document{}
d.Write() // accès direct
Avec l'agrégation, vous donnez au champ un nom explicite, ce qui nécessite d'utiliser ce nom pour accéder au type interne :
type Document struct {
writer Writer // agrégation - champ nommé
}
d := Document{writer: Writer{}}
d.writer.Write() // doit utiliser le nom du champ
La différence clé est la relation que vous exprimez. L'imbrication (Embedding) suggère une relation « est un » (is-a) où le type externe adopte l'identité du type interne. L'agrégation suggère une relation « a un » (has-a) où le type externe contient simplement le type interne.
Utilisez l'imbrication (embedding) lorsque vous voulez que le type externe satisfasse les interfaces implémentées par le type interne, ou lorsque l'accès direct aux méthodes rend l'API plus propre. Utilisez l'agrégation lorsque le type interne est un détail d'implémentation qui ne devrait pas être exposé, ou lorsque vous voulez un contrôle explicite sur la façon dont le type interne est accédé.
Défi
FacileConstruisons un système de génération de rapports qui démontre quand utiliser l'imbrication (embedding) par rapport à l'agrégation (aggregation). Vous allez créer un système où certains composants doivent être directement accessibles (imbrication) tandis que d'autres doivent rester des détails d'implémentation internes (agrégation).
Vous organiserez votre code sur trois fichiers :
formatter.go: Créez une structureFormatteravec un champStyle(string) et une méthodeFormat(text string) stringqui retourne le texte entouré par le style, comme[Style] text [/Style]. Ce sera une aide interne qui ne devrait pas être directement exposée.report.go: Créez deux structures qui gèrent la génération de rapports différemment :- Une structure
Metadataavec les champsAuthoretDate—cela représente des informations publiques qui devraient être directement accessibles - Une structure
Reportavec un champTitlequi imbriqueMetadata(pour un accès direct à l'auteur et à la date) mais utilise l'agrégation pourFormatter(en tant que détail d'implémentation interne avec un champ nomméformatter)
Generate() stringàReportqui utilise le formateur interne pour formater le titre et inclut les métadonnées.- Une structure
main.go: Lisez les détails du rapport depuis l'entrée, créez unReportavec à la fois les métadonnées imbriquées et un formateur agrégé. Démontrez la différence en accédant à l'auteur directement sur le rapport (via l'imbrication) puis en générant le rapport formaté (qui utilise le formateur caché en interne).
Les entrées suivantes seront fournies :
- Ligne 1 : Titre du rapport
- Ligne 2 : Nom de l'auteur
- Ligne 3 : Date
- Ligne 4 : Style du formateur
Votre méthode Format doit retourner :
[Style] text [/Style]Votre méthode Generate doit retourner :
[formatted title]
Author: [Author], Date: [Date]Dans votre fichier principal, affichez trois lignes :
- L'auteur accédé directement sur le
Report(démontrant que l'imbrication promeut le champ) - La date accédée via le nom du type imbriqué (
report.Metadata.Date) - Le résultat de l'appel à
Generate()
Par exemple, avec Quarterly Results, Jane Doe, 2024-01-15, et BOLD, votre sortie devrait être :
Jane Doe
2024-01-15
[BOLD] Quarterly Results [/BOLD]
Author: Jane Doe, Date: 2024-01-15Remarquez comment Author et Date sont accessibles directement sur le Report grâce à l'imbrication, tandis que le formateur reste caché—vous ne pouvez pas appeler report.Format() directement car il est agrégé avec un champ nommé. Cela démontre la relation « est un » (is-a) par rapport à « a un » (has-a) en pratique.
Aide-mémoire
Go prend en charge deux façons d'inclure une structure dans une autre : l'imbrication (embedding) et l'agrégation.
L'imbrication omet le nom du champ, promouvant les champs et les méthodes du type interne vers la structure externe :
type Writer struct{}
func (w Writer) Write() string { return "writing" }
type Document struct {
Writer // embedding - no field name
}
d := Document{}
d.Write() // direct access to embedded method
L'agrégation utilise un nom de champ explicite, nécessitant ce nom pour accéder au type interne :
type Document struct {
writer Writer // aggregation - named field
}
d := Document{writer: Writer{}}
d.writer.Write() // must use field name
Quand utiliser l'un ou l'autre :
- Imbrication : Exprime une relation de type « est un » (is-a). Utilisez-la lorsque le type externe doit satisfaire les interfaces du type interne, ou lorsque l'accès direct aux méthodes améliore l'API.
- Agrégation : Exprime une relation de type « a un » (has-a). Utilisez-la lorsque le type interne est un détail d'implémentation qui ne doit pas être exposé, ou lorsque vous souhaitez un contrôle explicite sur l'accès.
Avec l'imbrication, vous pouvez toujours accéder aux champs via le nom du type imbriqué si nécessaire :
type Metadata struct {
Author string
}
type Report struct {
Metadata // embedded
}
r := Report{Metadata: Metadata{Author: "Jane"}}
r.Author // direct access via promotion
r.Metadata.Author // explicit access via type name
Essayez vous-même
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
title := scanner.Text()
scanner.Scan()
author := scanner.Text()
scanner.Scan()
date := scanner.Text()
scanner.Scan()
style := scanner.Text()
// TODO: Créer une struct Metadata avec author et date
// TODO: Créer un Formatter avec le style donné
// TODO: Créer un Report qui imbrique Metadata et agrège Formatter
// TODO: Afficher author accédé directement sur le Report (l'imbrication promeut le champ)
// TODO: Afficher date accédé via le nom du type imbriqué (report.Metadata.Date)
// TODO: Afficher le résultat de l'appel à Generate()
// Supprimer les avertissements de variables inutilisées (retirez-les lorsque vous utilisez les variables)
_ = title
_ = author
_ = date
_ = style
}
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