Collections polymorphes
Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 44 sur 107.
L'une des applications les plus puissantes des interfaces est le stockage de différents types dans la même collection. Une slice d'un type interface peut contenir n'importe quelle valeur qui satisfait cette interface, vous permettant de regrouper des types liés mais différents.
Considérez un scénario dans lequel vous devez gérer différentes formes. Au lieu d'utiliser des slices distinctes pour chaque type, vous pouvez utiliser une seule slice du type interface :
type Shape interface {
Area() float64
}
type Circle struct{ Radius float64 }
func (c Circle) Area() float64 { return 3.14159 * c.Radius * c.Radius }
type Rectangle struct{ Width, Height float64 }
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func main() {
shapes := []Shape{
Circle{Radius: 5},
Rectangle{Width: 4, Height: 3},
Circle{Radius: 2},
}
for _, s := range shapes {
fmt.Printf("Area: %.2f\n", s.Area())
}
}
La slice shapes contient à la fois des cercles et des rectangles. Lors de l'itération, chaque élément répond à Area() selon son type réel. Il s'agit du polymorphisme appliqué aux collections — la même boucle gère tous les types de formes de manière uniforme.
Les collections polymorphes sont essentielles lors de la construction de systèmes qui traitent des éléments variés : un système de notification envoyant des e-mails et des SMS, un jeu mettant à jour différents types d'ennemis, ou un processeur de documents gérant plusieurs formats de fichiers. La collection ne se soucie pas des types spécifiques — seulement que chaque élément fournisse le comportement requis.
Défi
FacileConstruisons un système de gestion de tâches qui gère différents types de tâches via une interface unifiée. Vous allez créer divers types de tâches et les traiter tous ensemble dans une collection polymorphe.
Vous organiserez votre code sur trois fichiers :
task.go: Définissez une interfaceTaskqui requiert une méthodeSummary() string. Cette interface sera le contrat commun que tous les types de tâches doivent remplir.types.go: Créez trois types de tâches différents qui implémentent chacun l'interfaceTask:BugFixavec les champsID(string) etSeverity(string)—sa méthodeSummary()retourneBug #[ID] ([Severity])Featureavec les champsName(string) etPoints(int)—sa méthodeSummary()retourneFeature: [Name] - [Points] ptsDocumentationavec le champTopic(string)—sa méthodeSummary()retourneDocs: [Topic]
main.go: Créez une fonction nomméePrintBacklogqui accepte une slice deTasket affiche le résumé de chaque tâche sur sa propre ligne. Lisez les détails des tâches depuis l'entrée standard, créez une instance de chaque type de tâche, rassemblez-les toutes dans une seule slice[]Task, et passez-la àPrintBacklog.
Les entrées suivantes seront fournies :
- Ligne 1 : Bug ID
- Ligne 2 : Bug severity
- Ligne 3 : Feature name
- Ligne 4 : Feature points (entier)
- Ligne 5 : Documentation topic
Par exemple, avec 1042, critical, Dark Mode, 8, et API Reference, votre sortie devrait être :
Bug #1042 (critical)
Feature: Dark Mode - 8 pts
Docs: API ReferenceLa puissance ici réside dans le fait que PrintBacklog n'a pas besoin d'une logique distincte pour les bugs, les fonctionnalités ou la documentation—elle itère simplement à travers la slice et appelle Summary() sur chaque élément. Chaque type de tâche répond avec son propre format unique, démontrant comment les collections polymorphes vous permettent de traiter des types divers de manière uniforme.
Aide-mémoire
Une tranche (slice) d'un type interface peut contenir n'importe quelle valeur qui satisfait cette interface, permettant ainsi des collections polymorphes :
type Shape interface {
Area() float64
}
type Circle struct{ Radius float64 }
func (c Circle) Area() float64 { return 3.14159 * c.Radius * c.Radius }
type Rectangle struct{ Width, Height float64 }
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func main() {
shapes := []Shape{
Circle{Radius: 5},
Rectangle{Width: 4, Height: 3},
Circle{Radius: 2},
}
for _, s := range shapes {
fmt.Printf("Area: %.2f\n", s.Area())
}
}
La tranche shapes contient différents types (Circle et Rectangle) qui implémentent tous l'interface Shape. Lors de l'itération, chaque élément répond à Area() selon son type réel — c'est le polymorphisme appliqué aux collections.
Ce modèle est utile pour traiter uniformément des éléments variés : des systèmes de notification gérant différents types de messages, des jeux mettant à jour divers types d'ennemis, ou des processeurs de documents gérant plusieurs formats de fichiers.
Essayez vous-même
package main
import (
"fmt"
)
// TODO: Créer une fonction PrintBacklog qui accepte une slice de Task
// et affiche le résumé de chaque tâche sur sa propre ligne
func main() {
// Lire l'entrée
var bugID string
var bugSeverity string
var featureName string
var featurePoints int
var docTopic string
fmt.Scanln(&bugID)
fmt.Scanln(&bugSeverity)
fmt.Scanln(&featureName)
fmt.Scanln(&featurePoints)
fmt.Scanln(&docTopic)
// TODO: Créer un exemplaire de chaque type de tâche (BugFix, Feature, Documentation)
// TODO: Collecter toutes les tâches dans une seule slice []Task
// TODO: Appeler PrintBacklog avec la slice de tâches
}
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