Le Duck Typing en Go
Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 42 sur 107.
Le terme duck typing vient du dicton : « Si cela marche comme un canard et cancane comme un canard, alors c'est un canard. » En Go, cela signifie qu'un type n'a pas besoin de déclarer explicitement qu'il implémente une interface — il lui suffit d'avoir les bonnes méthodes.
Considérez cette interface et deux types complètement distincts :
type Quacker interface {
Quack() string
}
type Duck struct{}
func (d Duck) Quack() string { return "Quack!" }
type Robot struct{}
func (r Robot) Quack() string { return "Beep-quack!" }
Ni Duck ni Robot ne mentionnent Quacker nulle part dans leurs définitions. Pourtant, les deux satisfont l'interface simplement parce qu'ils possèdent une méthode Quack() avec la signature correcte :
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quack!
MakeItQuack(Robot{}) // Beep-quack!
}
Cette satisfaction implicite est puissante car elle vous permet de définir des interfaces après que les types existent déjà. Vous pouvez créer une interface qui correspond à des types provenant de packages externes sans modifier leur code source. Les types n'ont pas besoin de connaître votre interface — ils ont simplement besoin de se comporter correctement.
Le duck typing en Go vous offre la flexibilité des langages dynamiques tout en maintenant la sécurité du typage à la compilation. Le compilateur vérifie que les types possèdent réellement les méthodes requises avant de permettre leur utilisation en tant que valeurs d'interface.
Défi
FacileConstruisons un système de messagerie qui démontre le duck typing en action. Vous allez créer des types complètement indépendants qui peuvent tous envoyer des messages via une interface commune — sans qu'aucun d'entre eux ne déclare explicitement l'implémenter.
Vous organiserez votre code sur trois fichiers :
messengers.go: Créez trois structures indépendantes qui partagent par hasard le même comportement :Phoneavec un champNumber— sa méthodeSendMessage(text string) stringretourne"SMS to [Number]: [text]"Computeravec un champEmail— sa méthodeSendMessage(text string) stringretourne"Email to [Email]: [text]"Pigeonavec un champName— sa méthodeSendMessage(text string) stringretourne"[Name] carries: [text]"
sender.go: Définissez une interfaceMessengerqui requiert une méthodeSendMessage(text string) string. Créez une fonction nomméeBroadcastqui prend une tranche (slice) deMessengeret une chaîne de caractères pour le message, puis retourne une tranche de chaînes contenant le résultat de l'appel àSendMessagesur chaque messager.main.go: Lisez les détails depuis l'entrée standard, créez un exemplaire de chaque type de messager, rassemblez-les dans une tranche deMessenger, et utilisezBroadcastpour envoyer un message à travers chacun d'eux. Affichez chaque résultat sur sa propre ligne.
Les entrées suivantes seront fournies :
- Ligne 1 : Numéro de téléphone
- Ligne 2 : Adresse e-mail
- Ligne 3 : Nom du pigeon
- Ligne 4 : Message à diffuser
Par exemple, avec "555-1234", "alice@mail.com", "Winston", et "Hello World", votre sortie devrait être :
SMS to 555-1234: Hello World
Email to alice@mail.com: Hello World
Winston carries: Hello WorldL'idée clé ici est que Phone, Computer, et Pigeon n'ont rien en commun — ils ne partagent pas de type de base et n'implémentent pas explicitement Messenger. Pourtant, parce qu'ils "cancanent" tous de la même manière (ils possèdent la méthode correspondante), Go permet de les utiliser tous comme des valeurs de type Messenger. C'est le duck typing à l'œuvre.
Aide-mémoire
En Go, le duck typing signifie qu'un type n'a pas besoin de déclarer explicitement qu'il implémente une interface — il lui suffit de posséder les méthodes requises avec des signatures correspondantes.
Un type satisfait implicitement une interface s'il possède toutes les méthodes requises par l'interface :
type Quacker interface {
Quack() string
}
type Duck struct{}
func (d Duck) Quack() string { return "Quack!" }
type Robot struct{}
func (r Robot) Quack() string { return "Beep-quack!" }
Duck et Robot satisfont tous deux l'interface Quacker sans le mentionner dans leurs définitions :
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quack!
MakeItQuack(Robot{}) // Beep-quack!
}
Cette satisfaction implicite vous permet de définir des interfaces après que les types existent déjà, et même de créer des interfaces qui correspondent à des types de packages externes sans modifier leur code source. Le compilateur vérifie au moment de la compilation que les types possèdent les méthodes requises, offrant ainsi une sécurité de typage tout en conservant de la souplesse.
Essayez vous-même
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Lire le numéro de téléphone
scanner.Scan()
phoneNumber := scanner.Text()
// Lire l'adresse e-mail
scanner.Scan()
email := scanner.Text()
// Lire le nom du pigeon
scanner.Scan()
pigeonName := scanner.Text()
// Lire le message à diffuser
scanner.Scan()
message := scanner.Text()
// TODO : Créer un de chaque type de messager (Phone, Computer, Pigeon)
// TODO : Rassembler tous les messagers dans une tranche (slice) de Messenger
// TODO : Utiliser Broadcast pour envoyer le message via tous les messagers
// TODO : Imprimer chaque résultat sur sa propre ligne
// Espace réservé pour utiliser les variables (à supprimer lors de l'implémentation)
_ = phoneNumber
_ = email
_ = pigeonName
_ = message
}
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