Menu
Coddy logo textTech

Structures génériques

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

Tout comme les fonctions, les structs peuvent également avoir des paramètres de type. Une struct générique vous permet de créer des structures de données qui fonctionnent avec n'importe quel type tout en maintenant la sécurité des types. C'est parfait pour construire des conteneurs réutilisables comme des piles, des files d'attente ou des paires clé-valeur.

Pour définir une structure générique, placez le paramètre de type après le nom de la structure :

type Box[T any] struct {
    Value T
}

func main() {
    intBox := Box[int]{Value: 42}
    strBox := Box[string]{Value: "hello"}
    
    fmt.Println(intBox.Value)  // 42
    fmt.Println(strBox.Value)  // hello
}

Contrairement aux fonctions génériques, vous devez spécifier explicitement le type lors de la création d'une instance d'une structure générique. Go ne peut pas inférer le type uniquement à partir du contexte.

Les structures génériques peuvent avoir plusieurs paramètres de type, ce qui les rend idéales pour des structures comme des paires ou des maps :

type Pair[K, V any] struct {
    Key   K
    Value V
}

p := Pair[string, int]{Key: "age", Value: 25}
fmt.Printf("%s: %d\n", p.Key, p.Value)  // age: 25

Vous pouvez également appliquer des contraintes pour garantir que les types stockés prennent en charge des opérations spécifiques :

type NumberBox[T int | float64] struct {
    Value T
}

nb := NumberBox[float64]{Value: 3.14}

Les structures génériques constituent le fondement de la création de structures de données réutilisables et de type sûr en Go. Dans la prochaine leçon, vous apprendrez comment définir des méthodes sur ces types génériques.

challenge icon

Défi

Facile

Construisons un système d'inventaire générique capable de suivre différents types d'articles ! Vous allez créer des structures de conteneurs réutilisables qui fonctionnent avec n'importe quel type tout en maintenant une sécurité de type complète.

Vous organiserez votre code sur deux fichiers :

  • inventory.go : Définissez vos structures de conteneurs génériques.

    Créez une structure générique Item[T any] avec deux champs : Name (string) et Data (de type T). Cela représente n'importe quel article avec des données associées d'un type flexible.

    Créez une structure générique Container[T any] avec un seul champ Items qui contient une tranche (slice) de Item[T]. Ce conteneur peut stocker plusieurs articles du même type de données.

    Créez une structure générique contrainte PricedItem[T int | float64] avec trois champs : Name (string), Quantity (int), et Price (de type T). La contrainte garantit que les prix sont toujours numériques.

    Implémentez une fonction NewContainer[T any]() *Container[T] qui crée et retourne un pointeur vers un Container vide.

    Implémentez une méthode Add sur *Container[T] qui prend un nom (string) et des données (T), crée un Item, et l'ajoute à la tranche Items du conteneur.

    Implémentez une méthode Count sur Container[T] qui retourne le nombre d'articles dans le conteneur.

  • main.go : Démontrez vos structures génériques avec différents types.

    Lisez un type d'article (string, int, ou priced), puis lisez un nombre suivi des détails de l'article.

    Pour le type string : Lisez des paires de nom et de données de type chaîne. Créez un Container[string], ajoutez tous les articles, puis affichez chaque article sous la forme [Name]: [Data] suivi du nombre total.

    Pour le type int : Lisez des paires de nom et de données entières. Créez un Container[int], ajoutez tous les articles, puis affichez chaque article sous la forme [Name]: [Data] suivi du nombre total.

    Pour le type priced : Lisez des triplets de nom, quantité et prix (en tant que float). Créez des instances de PricedItem[float64] directement et affichez chacune sous la forme [Name] x[Quantity] @ [Price] avec le prix affichant une décimale.

    Affichez la ligne du total comme ceci : Total items: [count]

Les entrées suivantes seront fournies :

  • Ligne 1 : Type d'article (string, int, ou priced)
  • Ligne 2 : Nombre d'articles (entier)
  • Lignes suivantes : Détails de l'article selon le type

Par exemple, avec :

string
3
Book
Fiction Novel
Pen
Blue Ink
Notebook
Lined Paper

Votre sortie devrait être :

Book: Fiction Novel
Pen: Blue Ink
Notebook: Lined Paper
Total items: 3

Et avec :

int
2
Apples
50
Oranges
30

Votre sortie devrait être :

Apples: 50
Oranges: 30
Total items: 2

Et avec :

priced
2
Widget
10
19.99
Gadget
5
49.50

Votre sortie devrait être :

Widget x10 @ 19.9
Gadget x5 @ 49.5
Total items: 2

Remarquez comment la même structure Container fonctionne parfaitement avec les chaînes de caractères et les entiers, tandis que PricedItem utilise une contrainte pour garantir que seuls les types numériques peuvent être utilisés pour les prix. Vous devez explicitement spécifier le paramètre de type lors de la création d'instances de ces structures génériques.

Aide-mémoire

Une structure générique vous permet de créer des structures de données qui fonctionnent avec n'importe quel type tout en maintenant la sécurité des types.

Pour définir une structure générique, placez le paramètre de type après le nom de la structure :

type Box[T any] struct {
    Value T
}

Lors de la création d'une instance d'une structure générique, vous devez spécifier explicitement le type (Go ne peut pas l'inférer) :

intBox := Box[int]{Value: 42}
strBox := Box[string]{Value: "hello"}

Les structures génériques peuvent avoir plusieurs paramètres de type :

type Pair[K, V any] struct {
    Key   K
    Value V
}

p := Pair[string, int]{Key: "age", Value: 25}

Vous pouvez appliquer des contraintes pour garantir que les types stockés prennent en charge des opérations spécifiques :

type NumberBox[T int | float64] struct {
    Value T
}

nb := NumberBox[float64]{Value: 3.14}

Les structures génériques peuvent contenir des champs qui utilisent le paramètre de type, y compris des slices :

type Container[T any] struct {
    Items []T
}

Vous pouvez définir des méthodes sur des structures génériques et créer des fonctions constructeurs génériques :

func NewContainer[T any]() *Container[T] {
    return &Container[T]{Items: []T{}}
}

func (c *Container[T]) Add(item T) {
    c.Items = append(c.Items, item)
}

Essayez vous-même

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
)

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Lire le type d'élément
	scanner.Scan()
	itemType := scanner.Text()
	
	// Lire le nombre
	scanner.Scan()
	count, _ := strconv.Atoi(scanner.Text())
	
	switch itemType {
	case "string":
		// TODO: Créer un Container[string] en utilisant NewContainer
		// TODO: Lire 'count' éléments (chacun a un nom sur une ligne, des données sur la ligne suivante)
		// TODO: Ajouter chaque élément au conteneur
		// TODO: Afficher chaque élément comme "[Name]: [Data]"
		// TODO: Afficher "Total items: [count]" en utilisant la méthode Count
		
	case "int":
		// TODO: Créer un Container[int] en utilisant NewContainer
		// TODO: Lire 'count' éléments (chacun a un nom sur une ligne, des données entières sur la ligne suivante)
		// TODO: Ajouter chaque élément au conteneur
		// TODO: Afficher chaque élément comme "[Name]: [Data]"
		// TODO: Afficher "Total items: [count]" en utilisant la méthode Count
		
	case "priced":
		// TODO: Créer une slice pour contenir des instances de PricedItem[float64]
		// TODO: Lire 'count' éléments (chacun a un nom, une quantité et un prix sur des lignes séparées)
		// TODO: Créer un PricedItem[float64] pour chacun et l'ajouter à la slice
		// TODO: Afficher chaque élément comme "[Name] x[Quantity] @ [Price]" avec le prix affichant une décimale
		// TODO: Afficher "Total items: [count]"
	}
	
	_ = scanner // Utiliser scanner pour lire l'entrée
	_ = count   // Utiliser count pour la boucle
}
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