Implémentation implicite
Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 26 sur 107.
Dans de nombreux langages orientés objet comme Java ou C#, vous devez déclarer explicitement qu'un type implémente une interface en utilisant des mots-clés comme implements. Go adopte une approche différente : l'implémentation d'une interface est implicite.
Un type satisfait une interface simplement en implémentant toutes ses méthodes. Il n'est pas nécessaire de déclarer cette relation explicitement. Le compilateur le détermine automatiquement.
type Writer interface {
Write(data string) int
}
type FileWriter struct {
Filename string
}
// FileWriter implémente implicitement Writer
func (f FileWriter) Write(data string) int {
fmt.Println("Writing to", f.Filename)
return len(data)
}
Remarquez qu'il n'y a pas de déclaration implements Writer sur FileWriter. Parce que FileWriter possède une méthode Write(data string) int correspondant à la signature de l'interface, elle satisfait automatiquement l'interface Writer.
Cette conception a une conséquence puissante : vous pouvez définir des interfaces après que les types existent déjà. Si une bibliothèque tierce possède un type avec les méthodes dont vous avez besoin, vous pouvez créer une interface qu'il satisfait déjà sans modifier le code original.
func Save(w Writer, content string) {
w.Write(content)
}
func main() {
fw := FileWriter{Filename: "data.txt"}
Save(fw, "Hello!") // Fonctionne car FileWriter satisfait Writer
}
L'implémentation implicite permet de garder le code Go découplé et flexible. Les types n'ont pas besoin de connaître les interfaces à l'avance, ce qui facilite la composition de systèmes à partir de parties indépendantes.
Défi
FacileConstruisons un système de distribution de messages qui démontre comment les types satisfont implicitement les interfaces en Go. Vous allez créer différents types de messagers qui satisfont tous la même interface, sans jamais déclarer explicitement cette relation.
Vous organiserez votre code sur deux fichiers :
messengers.go: Définissez une interfaceMessengeravec une seule méthodeSend(message string) string. Ensuite, créez deux structs qui satisferont implicitement cette interface :EmailMessengeravec un champAddress(string)SMSMessengeravec un champPhoneNumber(string)
Sendqui retourne une chaîne de caractères décrivant l'envoi. Aucune des deux structs ne doit déclarer explicitement qu'elle implémenteMessenger— Go le déterminera automatiquement en fonction des signatures de méthode.main.go: Créez une fonction appeléeDeliverMessagequi accepte n'importe quelMessengeret une chaîne de caractères de message, puis retourne le résultat de l'appel àSend. Lisez les informations de contact à partir de l'entrée, créez les deux types de messagers et utilisez votre fonction pour distribuer les messages via chacun d'eux.
Les entrées suivantes seront fournies :
- Ligne 1 : Adresse e-mail
- Ligne 2 : Numéro de téléphone
- Ligne 3 : Message à envoyer
Vos méthodes Send doivent retourner des chaînes de caractères dans ces formats :
- EmailMessenger :
Email to [Address]: [message] - SMSMessenger :
SMS to [PhoneNumber]: [message]
Par exemple, avec alice@example.com, 555-1234, et Hello!, votre sortie devrait être :
Email to alice@example.com: Hello!
SMS to 555-1234: Hello!L'idée clé ici est que votre fonction DeliverMessage accepte n'importe quel Messenger, et que EmailMessenger tout comme SMSMessenger satisfont cette interface simplement en ayant une méthode Send correspondante. Il n'y a aucun mot-clé implements nulle part — l'implémentation implicite de Go gère tout.
Aide-mémoire
En Go, l'implémentation d'une interface est implicite. Un type satisfait une interface simplement en implémentant toutes ses méthodes — aucune déclaration explicite n'est nécessaire.
type Writer interface {
Write(data string) int
}
type FileWriter struct {
Filename string
}
// FileWriter implémente implicitement Writer
func (f FileWriter) Write(data string) int {
fmt.Println("Writing to", f.Filename)
return len(data)
}
Il n'y a pas de déclaration implements Writer. Parce que FileWriter possède une méthode Write(data string) int correspondant à la signature de l'interface, il satisfait automatiquement l'interface Writer.
Vous pouvez utiliser n'importe quel type qui satisfait une interface sans que le type ne connaisse l'interface :
func Save(w Writer, content string) {
w.Write(content)
}
func main() {
fw := FileWriter{Filename: "data.txt"}
Save(fw, "Hello!") // Fonctionne car FileWriter satisfait Writer
}
Cette conception vous permet de définir des interfaces après que les types existent déjà, rendant le code découplé et flexible.
Essayez vous-même
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: Créer une fonction DeliverMessage qui accepte n'importe quel Messenger
// et une chaîne de caractères message, puis retourne le résultat de l'appel à Send
func main() {
reader := bufio.NewReader(os.Stdin)
// Lire l'adresse e-mail
email, _ := reader.ReadString('\n')
email = email[:len(email)-1]
// Lire le numéro de téléphone
phone, _ := reader.ReadString('\n')
phone = phone[:len(phone)-1]
// Lire le message
message, _ := reader.ReadString('\n')
if len(message) > 0 && message[len(message)-1] == '\n' {
message = message[:len(message)-1]
}
// TODO: Créer un EmailMessenger avec l'adresse e-mail
// TODO: Créer un SMSMessenger avec le numéro de téléphone
// TODO: Utiliser DeliverMessage pour envoyer le message via chaque messager
// et afficher les résultats
}
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