Le polymorphisme via les interfaces
Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 41 sur 107.
Le polymorphisme permet de traiter différents types de manière uniforme via une interface partagée. En Go, cela est réalisé entièrement via des interfaces, sans héritage ni hiérarchies de classes.
Lorsqu'une fonction accepte un type d'interface comme paramètre, n'importe quel type concret qui implémente cette interface peut lui être passé. La fonction n'a pas besoin de connaître le type spécifique — elle se soucie uniquement du comportement défini par l'interface :
type Speaker interface {
Speak() string
}
type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof!" }
type Cat struct{ Name string }
func (c Cat) Speak() string { return "Meow!" }
func MakeSound(s Speaker) {
fmt.Println(s.Speak())
}
Maintenant MakeSound fonctionne avec n'importe quel type qui possède une méthode Speak() :
func main() {
dog := Dog{Name: "Rex"}
cat := Cat{Name: "Whiskers"}
MakeSound(dog) // Wouf !
MakeSound(cat) // Miaou !
}
Le même appel de fonction produit un comportement différent selon le type réel passé. C'est le polymorphisme en action. La fonction MakeSound est écrite une seule fois mais fonctionne avec un nombre illimité de types, tant qu'ils satisfont l'interface Speaker.
Cette approche rend votre code flexible et extensible. L'ajout d'un nouveau type qui parle ne nécessite aucune modification des fonctions existantes — il suffit d'implémenter l'interface, et cela fonctionne automatiquement.
Défi
FacileConstruisons un système de description de véhicules qui démontre le polymorphisme en action. Vous allez créer différents types de véhicules qui partagent tous un comportement commun via une interface, puis écrire une fonction unique qui fonctionne avec n'importe quel véhicule.
Vous organiserez votre code sur deux fichiers :
vehicles.go: Définissez une interfaceDescriberqui requiert une méthodeDescribe() string. Ensuite, créez trois types de véhicules qui implémentent chacun cette interface à leur manière :Caravec les champsBrandetModel—sa méthodeDescribe()retourne"Car: [Brand] [Model]"Motorcycleavec les champsBrandetEngineCC(int)—sa méthodeDescribe()retourne"Motorcycle: [Brand] [EngineCC]cc"Bicycleavec le champType(comme "Mountain" ou "Road")—sa méthodeDescribe()retourne"Bicycle: [Type]"
main.go: Créez une fonction appeléePrintDescriptionqui accepte n'importe quelDescriberet affiche le résultat de l'appel àDescribe(). Lisez les détails des véhicules depuis l'entrée standard, créez une instance de chaque type de véhicule, et passez chacune d'elles àPrintDescriptionpour démontrer que la même fonction fonctionne avec les trois types différents.
Les entrées suivantes seront fournies :
- Ligne 1 : Marque de la voiture (Car brand)
- Ligne 2 : Modèle de la voiture (Car model)
- Ligne 3 : Marque de la moto (Motorcycle brand)
- Ligne 4 : Cylindrée de la moto (Motorcycle engine CC - entier)
- Ligne 5 : Type de vélo (Bicycle type)
Par exemple, avec Toyota, Camry, Honda, 600, et Mountain, votre sortie devrait être :
Car: Toyota Camry
Motorcycle: Honda 600cc
Bicycle: MountainRemarquez comment PrintDescription n'a pas besoin de savoir s'il reçoit une Car, une Motorcycle ou un Bicycle—il appelle simplement Describe() et chaque type répond avec sa propre sortie unique. C'est le polymorphisme : une fonction, plusieurs comportements.
Aide-mémoire
Le polymorphisme permet de traiter différents types de manière uniforme via une interface partagée. En Go, cela est réalisé grâce aux interfaces, sans héritage ni hiérarchies de classes.
Lorsqu'une fonction accepte un type d'interface comme paramètre, n'importe quel type concret qui implémente cette interface peut être passé :
type Speaker interface {
Speak() string
}
type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof!" }
type Cat struct{ Name string }
func (c Cat) Speak() string { return "Meow!" }
func MakeSound(s Speaker) {
fmt.Println(s.Speak())
}
La même fonction fonctionne avec différents types :
func main() {
dog := Dog{Name: "Rex"}
cat := Cat{Name: "Whiskers"}
MakeSound(dog) // Woof!
MakeSound(cat) // Meow!
}
La fonction est écrite une seule fois mais fonctionne avec une infinité de types, tant qu'ils satisfont l'interface. L'ajout de nouveaux types ne nécessite aucune modification des fonctions existantes — il suffit d'implémenter l'interface.
Essayez vous-même
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// TODO: Créer une fonction appelée PrintDescription qui accepte n'importe quel Describer
// et affiche le résultat de l'appel à Describe()
func main() {
reader := bufio.NewReader(os.Stdin)
// Lire les détails de la voiture
carBrand, _ := reader.ReadString('\n')
carBrand = strings.TrimSpace(carBrand)
carModel, _ := reader.ReadString('\n')
carModel = strings.TrimSpace(carModel)
// Lire les détails de la moto
motoBrand, _ := reader.ReadString('\n')
motoBrand = strings.TrimSpace(motoBrand)
motoEngineStr, _ := reader.ReadString('\n')
motoEngineStr = strings.TrimSpace(motoEngineStr)
motoEngine, _ := strconv.Atoi(motoEngineStr)
// Lire les détails du vélo
bicycleType, _ := reader.ReadString('\n')
bicycleType = strings.TrimSpace(bicycleType)
// TODO: Créer un Car, un Motorcycle et un Bicycle en utilisant les valeurs d'entrée
// TODO: Appeler PrintDescription pour chaque véhicule afin de démontrer le polymorphisme
fmt.Println("TODO: Print vehicle descriptions")
}
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