Menu
Coddy logo textTech

Contournement pour les méthodes génériques

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

Go a une limitation notable : vous ne pouvez pas définir de méthodes avec leurs propres paramètres de type sur un struct. Bien que les structs génériques fonctionnent très bien, ajouter une méthode qui introduit un nouveau paramètre de type n'est pas autorisé.

Ce code ne compilera pas :

type Box[T any] struct {
    Value T
}

// ERREUR : une méthode ne doit pas avoir de paramètres de type
func (b Box[T]) Convert[U any](fn func(T) U) U {
    return fn(b.Value)
}

La solution de contournement consiste à utiliser une fonction générique autonome au lieu d'une méthode :

type Box[T any] struct {
    Value T
}

// Méthode régulière - utilise le paramètre de type de la structure
func (b Box[T]) Get() T {
    return b.Value
}

// Fonction autonome - peut avoir ses propres paramètres de type
func Convert[T, U any](b Box[T], fn func(T) U) U {
    return fn(b.Value)
}

func main() {
    intBox := Box[int]{Value: 42}
    
    // Utiliser la méthode
    fmt.Println(intBox.Get())  // 42
    
    // Utiliser la fonction autonome
    strResult := Convert(intBox, func(n int) string {
        return fmt.Sprintf("Number: %d", n)
    })
    fmt.Println(strResult)  // Number: 42
}

Les méthodes sur les structures génériques peuvent toujours utiliser le paramètre de type T de la structure. La restriction s'applique uniquement à l'introduction de paramètres de type supplémentaires dans la signature de la méthode.

Lorsque vous avez besoin de cette flexibilité, une fonction générique qui prend la struct comme premier argument permet d'obtenir le même résultat.

challenge icon

Défi

Facile

Construisons une boîte à outils de transformation de données qui démontre le contournement de la limitation de Go sur les méthodes génériques ! Étant donné que les méthodes ne peuvent pas introduire de nouveaux paramètres de type, vous allez créer des fonctions génériques autonomes qui offrent la même flexibilité.

Vous organiserez votre code sur deux fichiers :

  • wrapper.go : Définissez votre conteneur générique et vos fonctions de transformation.

    Créez une structure générique Wrapper[T any] avec un seul champ Value de type T.

    Ajoutez une méthode Get sur Wrapper[T] qui retourne la valeur enveloppée. Cette méthode utilise le paramètre de type de la structure, ce qui est autorisé.

    Créez une fonction générique autonome Transform[T, U any](w Wrapper[T], fn func(T) U) U qui applique la fonction de transformation à la valeur du wrapper et retourne le résultat. Cette fonction a besoin de son propre paramètre de type U pour le type de sortie, c'est pourquoi elle doit être une fonction autonome plutôt qu'une méthode.

    Créez une autre fonction autonome TransformToString[T any](w Wrapper[T]) string qui convertit la valeur enveloppée en chaîne de caractères en utilisant fmt.Sprintf("%v", ...).

  • main.go : Démontrez le modèle de contournement avec différentes transformations.

    Lisez un indicateur de type (int ou string), puis lisez une valeur. Créez un Wrapper du type approprié.

    Pour une entrée int : Créez un Wrapper[int], puis utilisez Transform pour doubler la valeur (retournant un int), et utilisez TransformToString pour obtenir une représentation sous forme de chaîne.

    Pour une entrée string : Créez un Wrapper[string], puis utilisez Transform pour obtenir la longueur de la chaîne (retournant un int), et utilisez TransformToString pour obtenir la représentation sous forme de chaîne.

    Affichez les résultats dans ce format :

    Original: [value]
    Transformed: [transformed value]
    As String: [string representation]

Les entrées suivantes seront fournies :

  • Ligne 1 : Indicateur de type (int ou string)
  • Ligne 2 : La valeur

Par exemple, avec :

int
25

Votre sortie devrait être :

Original: 25
Transformed: 50
As String: 25

Et avec :

string
Hello World

Votre sortie devrait être :

Original: Hello World
Transformed: 11
As String: Hello World

L'idée clé est que Transform prend un Wrapper[T] comme premier argument et introduit un nouveau paramètre de type U pour le type de retour — ce qui ne serait pas possible en tant que méthode sur la structure. Ce modèle vous donne la flexibilité des transformations génériques tout en respectant les contraintes du système de types de Go.

Aide-mémoire

Go ne permet pas aux méthodes sur les structures génériques d'introduire leurs propres paramètres de type. Les méthodes peuvent uniquement utiliser les paramètres de type existants de la structure.

Ce code ne compilera pas :

type Box[T any] struct {
    Value T
}

// ERROR: method must have no type parameters
func (b Box[T]) Convert[U any](fn func(T) U) U {
    return fn(b.Value)
}

La solution consiste à utiliser des fonctions génériques autonomes au lieu de méthodes :

type Box[T any] struct {
    Value T
}

// Regular method - uses the struct's type parameter
func (b Box[T]) Get() T {
    return b.Value
}

// Standalone function - can have its own type parameters
func Convert[T, U any](b Box[T], fn func(T) U) U {
    return fn(b.Value)
}

func main() {
    intBox := Box[int]{Value: 42}
    
    // Use the method
    fmt.Println(intBox.Get())  // 42
    
    // Use the standalone function
    strResult := Convert(intBox, func(n int) string {
        return fmt.Sprintf("Number: %d", n)
    })
    fmt.Println(strResult)  // Number: 42
}

Les méthodes sur les structures génériques peuvent utiliser le paramètre de type de la structure. La restriction s'applique uniquement à l'introduction de paramètres de type supplémentaires dans la signature de la méthode. Lorsque vous avez besoin de cette flexibilité, utilisez une fonction générique qui prend la structure comme premier argument.

Essayez vous-même

package main

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

func main() {
	reader := bufio.NewReader(os.Stdin)
	
	// Lire l'indicateur de type
	typeIndicator, _ := reader.ReadString('\n')
	typeIndicator = strings.TrimSpace(typeIndicator)
	
	// Lire la valeur
	value, _ := reader.ReadString('\n')
	value = strings.TrimSpace(value)
	
	if typeIndicator == "int" {
		// Analyser la valeur entière
		num, _ := strconv.Atoi(value)
		
		// TODO: Créer un Wrapper[int] avec le nombre analysé
		
		// TODO: Utiliser Transform pour doubler la valeur (indice : passer une fonction qui double)
		
		// TODO: Utiliser TransformToString pour obtenir la représentation sous forme de chaîne
		
		// TODO: Imprimer les résultats au format requis :
		// Original: [value]
		// Transformed: [transformed value]
		// As String: [string representation]
		
		_ = num // Supprimer cette ligne lorsque vous utilisez num
	} else if typeIndicator == "string" {
		// TODO: Créer un Wrapper[string] avec la valeur
		
		// TODO: Utiliser Transform pour obtenir la longueur de la chaîne (indice : passer une fonction qui retourne len())
		
		// TODO: Utiliser TransformToString pour obtenir la représentation sous forme de chaîne
		
		// TODO: Imprimer les résultats au format requis :
		// Original: [value]
		// Transformed: [transformed value]
		// As String: [string representation]
		
		_ = value // Supprimer cette ligne lorsque vous utilisez value
	}
}
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