Inclusion ou 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’intégration promeut les champs et les méthodes vers la structure externe. Mais Go prend également en charge l’agrégation, dans laquelle vous incluez une autre structure en tant que champ nommé. Comprendre quand utiliser chaque approche est essentiel pour une bonne conception.
Avec l’intégration, 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 // embedding - 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 vous oblige à 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 essentielle réside dans la relation que vous exprimez. L’imbrication suggère une relation « est-un », où le type externe adopte l’identité du type interne. L’agrégation suggère une relation « possède-un », où le type externe se contente de contenir le type interne.
Utilisez l’intégration lorsque vous voulez que le type externe satisfasse aux interfaces implémentées par le type interne, ou lorsque l’accès direct aux méthodes rend l’API plus claire. 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 contrôler explicitement la manière dont le type interne est accessible.
Défi
FacileConstruisons un système de génération de rapports qui démontre quand utiliser l’embedding plutôt que l’aggregation. Vous allez créer un système dans lequel certains composants doivent être directement accessibles (embedding), tandis que d’autres doivent rester des détails d’implementation internes (aggregation).
Vous organiserez votre code sur trois fichiers :
formatter.go: Create une structureFormatteravec un champStyle(chaîne de caractères) et une méthodeFormat(text string) stringqui renvoie le texte entouré par le style, comme[Style] text [/Style]. Il s’agira d’un helper interne qui ne devrait pas être directement exposé.report.go: Create deux structures qui gèrent la génération de rapports différemment :- une structure
Metadataavec les champsAuthoretDate. Elle représente des informations publiques qui doivent être directement accessibles - une structure
Reportavec un champTitlequi embedsMetadata(pour un accès direct à l’auteur et à la date), mais utilise l’aggregation pourFormatter(comme détail d’implementation interne avec un champ nomméformatter)
Generate() stringàReportqui utilise le formatter 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 des métadonnées intégrées et un formatter agrégé. Démontrez la différence en accédant directement à l’auteur sur le rapport (via l’embedding), puis en générant le rapport formaté (qui utilise le formatter 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 Formatter
Votre méthode Format doit renvoyer :
[Style] text [/Style]Votre méthode Generate doit renvoyer :
[formatted title]
Author: [Author], Date: [Date]Dans votre fichier principal, affichez trois lignes :
- L’auteur accessed directement sur le Report (démontrant que l’embedding promeut le field)
- La date accessed via le nom du type embedded (
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’embedding, tandis que le formatter reste caché. Vous ne pouvez pas appeler directement report.Format(), car il est agrégé avec un champ nommé. Cela démontre concrètement la relation « is-a » par opposition à « has-a ».
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 l'author et la date
// TODO: Créer un Formatter avec le style donné
// TODO: Créer un Report qui embarque Metadata et agrège Formatter
// TODO: Afficher l'author accédé directement sur le Report (l'embedding promeut le champ)
// TODO: Afficher la date accédée via le nom du type embarqué (report.Metadata.Date)
// TODO: Afficher le résultat de l'appel à Generate()
// Supprimer les avertissements de variables non utilisé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 et non exportésIntroduction à la POO en GoStructs comme classesDéfinir des méthodes sur des structsRécepteurs pointeurs ou par valeurInitialisation des structsFonctions constructeursRécapitulatif - Calculatrice simple4Interfaces
Introduction aux interfacesImplémentation impliciteL’interface comme contratInterface vide (any)Assertion de typeCommutation de typeComposition d’interfacesInterfaces Stringer et ErrorRécapitulatif - Calculateur de formes7Encapsulation
Champs exportés vs non exportésEncapsulation au niveau du packageMéthodes Getter et SetterDissimulation de l’information en GoRécapitulatif – Fiches d’étudiants10Génériques (Go 1.18+)
Introduction aux génériquesParamètres de typeContraintes de typeStructures génériquesSolution de contournement pour les méthodes génériquesRécapitulatif - Collection générique2Plongée approfondie dans les types et les structs
Types de base et compositesDéfinitions de types personnalisésTags de structStructs anonymesStructs imbriquésValeurs zéro et valeurs par défautRécapitulatif - Carnet de contacts5La composition plutôt que l’héritage
Pourquoi Go n’a pas d’héritageBases de l’inclusion de structsPromotion des méthodesInclure plusieurs structsInclusion ou agrégationMasquage des méthodes inclusesRécapitulatif : hiérarchie des employés8Gestion des erreurs et POO
L’interface errorTypes d’erreurs personnalisésEnrobage des erreurs (fmt.Errorf)Erreurs sentinelleserrors.Is() et errors.As()Panic, Defer et RecoverRécapitulatif - Analyseur de fichiers11Bibliothèque standard et POO
io.Reader et io.Writersort.InterfaceInterface fmt.Stringerencoding/json avec des structsInterface http.HandlerRécapitulatif - Modèles d’API REST14Modèles de conception – Partie 2
Modèle CommandeModèle AdaptateurModèle DécorateurModèle Méthode modèleModèle ÉtatModèle CompositeMiddleware comme décorateur3Pointeurs et mémoire
Notions de base des pointeurs en GoPointeurs vers des structuresPassage par valeur ou par référenceLa fonction new()Garbage collection en GoRécapitulatif - Constructeur de listes chaînées6Polymorphisme en Go
Polymorphisme via les interfacesDuck typing en GoRègles de satisfaction des interfacesCollections polymorphesInjection de dépendancesRécapitulatif – Processeur de paiements9Concurrence et POO
Bases des GoroutinesCanaux et communicationCanaux tamponnés ou non tamponnésInstruction selectsync.Mutex et sync.RWMutexsync.WaitGroupConception de structures thread-safeRécapitulatif - Pool de workersEntraînez-vous par vous-même : Compilateur Go en ligne