Menu
Coddy logo textTech

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é.

challenge icon

Défi

Facile

Construisons 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 structure Formatter avec un champ Style (string) et une méthode Format(text string) string qui 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 Metadata avec les champs Author et Date—cela représente des informations publiques qui devraient être directement accessibles
    • Une structure Report avec un champ Title qui imbrique Metadata (pour un accès direct à l'auteur et à la date) mais utilise l'agrégation pour Formatter (en tant que détail d'implémentation interne avec un champ nommé formatter)
    Ajoutez une méthode Generate() string à Report qui utilise le formateur interne pour formater le titre et inclut les métadonnées.
  • main.go : Lisez les détails du rapport depuis l'entrée, créez un Report avec à 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 :

  1. L'auteur accédé directement sur le Report (démontrant que l'imbrication promeut le champ)
  2. La date accédée via le nom du type imbriqué (report.Metadata.Date)
  3. 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-15

Remarquez 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
}
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