Règles de satisfaction des interfaces
Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 43 sur 107.
Alors que Go utilise le duck typing pour la satisfaction implicite des interfaces, il existe des règles spécifiques qui déterminent si un type satisfait réellement une interface. Comprendre ces règles vous aide à éviter des bogues subtils.
La règle la plus importante concerne les récepteurs de pointeur. Si une méthode est définie avec un récepteur de pointeur, seul un pointeur vers ce type satisfait l'interface — et non la valeur elle-même :
type Saver interface {
Save() string
}
type Document struct{ Name string }
func (d *Document) Save() string { // récepteur de pointeur
return "Saved: " + d.Name
}
func Process(s Saver) {
fmt.Println(s.Save())
}
Parce que Save() a un récepteur de pointeur, seul *Document satisfait Saver :
func main() {
doc := Document{Name: "report.txt"}
Process(&doc) // fonctionne - le pointeur satisfait l'interface
// Process(doc) // erreur de compilation - la valeur ne satisfait pas l'interface
}
Cependant, l'inverse est plus flexible. Si une méthode a un récepteur de valeur, à la fois les types de valeur et de pointeur satisfont l'interface. Go déréférence automatiquement les pointeurs lors de l'appel de méthodes avec récepteur de valeur :
func (d Document) Info() string { // récepteur de valeur
return d.Name
}
// Document et *Document satisfont tous deux une interface nécessitant Info()
Cette asymétrie existe parce que Go peut toujours obtenir une valeur à partir d'un pointeur (par déréférencement), mais il ne peut pas toujours obtenir un pointeur à partir d'une valeur (la valeur peut ne pas être adressable). Garder cette règle à l'esprit permet d'éviter des erreurs de compilation confuses lors de l'utilisation des interfaces.
Défi
FacileConstruisons un système de configuration qui démontre comment les récepteurs de pointeur et de valeur affectent la satisfaction d'une interface. Vous allez créer des types où le choix du récepteur détermine si les valeurs, les pointeurs ou les deux peuvent être utilisés avec une interface.
Vous organiserez votre code sur trois fichiers :
config.go: Définissez une interfaceConfigurablequi requiert deux méthodes :GetValue() stringetSetValue(string). Créez ensuite deux types de configuration :ReadOnlyConfigavec un champValue—utilisez un récepteur de valeur pourGetValue()(retourne la Value) et un récepteur de pointeur pourSetValue()(met à jour la Value)Settingavec un champData—utilisez des récepteurs de valeur pour les deux méthodes (GetValue retourne Data, SetValue affiche simplement "Cannot modify" sans rien changer)
processor.go: Créez une fonction appeléeProcessConfigqui accepte unConfigurableet une nouvelle chaîne de caractères pour la valeur. Elle doit afficher la valeur actuelle en utilisantGetValue(), appelerSetValue()avec la nouvelle valeur, puis afficher à nouveau la valeur pour montrer les changements éventuels.main.go: Lisez les détails de configuration depuis l'entrée standard et démontrez les règles de satisfaction d'interface :- Créez un
ReadOnlyConfiget passez un pointeur àProcessConfig(requis carSetValuea un récepteur de pointeur) - Créez un
Settinget passez la valeur directement àProcessConfig(fonctionne car les deux méthodes ont des récepteurs de valeur)
- Créez un
Les entrées suivantes seront fournies :
- Ligne 1 : Valeur initiale pour ReadOnlyConfig
- Ligne 2 : Nouvelle valeur à définir pour ReadOnlyConfig
- Ligne 3 : Valeur initiale pour Setting
- Ligne 4 : Nouvelle valeur à tenter pour Setting
Votre fonction ProcessConfig doit afficher les résultats dans ce format :
Current: [value]
Current: [value after SetValue]Par exemple, avec debug, production, localhost, et remote, votre sortie devrait être :
Current: debug
Current: production
Current: localhost
Cannot modify
Current: localhostRemarquez comment ReadOnlyConfig change réellement sa valeur (parce que nous avons passé un pointeur), tandis que Setting reste inchangé (son SetValue avec un récepteur de valeur ne peut pas modifier l'original). L'idée clé est que les valeurs de ReadOnlyConfig seules ne satisferaient pas Configurable—seuls les pointeurs le font—alors que les valeurs de Setting fonctionnent directement parce que toutes ses méthodes utilisent des récepteurs de valeur.
Aide-mémoire
Go utilise le typage structurel (duck typing) pour la satisfaction implicite des interfaces, mais il existe des règles spécifiques concernant les récepteurs de pointeurs et de valeurs :
Règle du récepteur de pointeur
Si une méthode a un récepteur de pointeur, seul un pointeur vers ce type satisfait l'interface :
type Saver interface {
Save() string
}
type Document struct{ Name string }
func (d *Document) Save() string { // récepteur de pointeur
return "Saved: " + d.Name
}
func main() {
doc := Document{Name: "report.txt"}
Process(&doc) // fonctionne - le pointeur satisfait l'interface
// Process(doc) // erreur de compilation - la valeur ne satisfait pas l'interface
}Règle du récepteur de valeur
Si une méthode a un récepteur de valeur, le type valeur et le type pointeur satisfont tous deux l'interface :
func (d Document) Info() string { // récepteur de valeur
return d.Name
}
// Document et *Document satisfont tous deux une interface requérant Info()Pourquoi cette asymétrie existe
Go peut toujours obtenir une valeur à partir d'un pointeur (par déréférencement), mais ne peut pas toujours obtenir un pointeur à partir d'une valeur (la valeur peut ne pas être adressable).
Essayez vous-même
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Lire les entrées
scanner.Scan()
rocInitial := scanner.Text()
scanner.Scan()
rocNew := scanner.Text()
scanner.Scan()
settingInitial := scanner.Text()
scanner.Scan()
settingNew := scanner.Text()
// TODO: Créer un ReadOnlyConfig avec la valeur rocInitial
// TODO: Passer un POINTER à ProcessConfig (requis car SetValue a un récepteur de type pointeur)
// TODO: Créer un Setting avec la valeur settingInitial
// TODO: Passer la VALUE directement à ProcessConfig (fonctionne car les deux méthodes ont des récepteurs de type valeur)
// Utiliser ces variables pour éviter les erreurs de variables non utilisées
_ = rocInitial
_ = rocNew
_ = settingInitial
_ = settingNew
}
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