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.
Défi
FacileConstruisons 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 champValuede typeT.Ajoutez une méthode
GetsurWrapper[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) Uqui applique la fonction de transformation à la valeur du wrapper et retourne le résultat. Cette fonction a besoin de son propre paramètre de typeUpour 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]) stringqui convertit la valeur enveloppée en chaîne de caractères en utilisantfmt.Sprintf("%v", ...).main.go: Démontrez le modèle de contournement avec différentes transformations.Lisez un indicateur de type (
intoustring), puis lisez une valeur. Créez unWrapperdu type approprié.Pour une entrée
int: Créez unWrapper[int], puis utilisezTransformpour doubler la valeur (retournant un int), et utilisezTransformToStringpour obtenir une représentation sous forme de chaîne.Pour une entrée
string: Créez unWrapper[string], puis utilisezTransformpour obtenir la longueur de la chaîne (retournant un int), et utilisezTransformToStringpour 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 (
intoustring) - Ligne 2 : La valeur
Par exemple, avec :
int
25Votre sortie devrait être :
Original: 25
Transformed: 50
As String: 25Et avec :
string
Hello WorldVotre sortie devrait être :
Original: Hello World
Transformed: 11
As String: Hello WorldL'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
}
}
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