Duck Typing in Go
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 42 von 107.
Der Begriff Duck-Typing stammt von dem Sprichwort: „Wenn es wie eine Ente läuft und wie eine Ente quakt, dann ist es eine Ente.“ In Go bedeutet dies, dass ein Typ nicht explizit deklarieren muss, dass er ein Interface implementiert – er muss lediglich über die entsprechenden Methoden verfügen.
Betrachten Sie dieses Interface und zwei völlig voneinander unabhängige Typen:
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!" }
Weder Duck noch Robot erwähnen Quacker irgendwo in ihren Definitionen. Dennoch erfüllen beide das Interface einfach deshalb, weil sie eine Quack()-Methode mit der korrekten Signatur besitzen:
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quack!
MakeItQuack(Robot{}) // Beep-quack!
}
Diese implizite Erfüllung ist leistungsstark, da sie es Ihnen ermöglicht, Interfaces zu definieren, nachdem die Typen bereits existieren. Sie können ein Interface erstellen, das zu Typen aus externen Paketen passt, ohne deren Quellcode zu ändern. Die Typen müssen nichts über Ihr Interface wissen – sie müssen sich lediglich korrekt verhalten.
Duck-Typing in Go bietet Ihnen die Flexibilität dynamischer Sprachen, während die Typsicherheit zur Kompilierzeit erhalten bleibt. Der Compiler überprüft, ob Typen tatsächlich über die erforderlichen Methoden verfügen, bevor er zulässt, dass sie als Interface-Werte verwendet werden.
Aufgabe
EinfachLassen Sie uns ein Nachrichtensystem bauen, das Duck-Typing in Aktion demonstriert. Sie werden völlig voneinander unabhängige Typen erstellen, die alle Nachrichten über eine gemeinsame Schnittstelle senden können – ohne dass einer von ihnen explizit deklariert, diese zu implementieren.
Sie werden Ihren Code über drei Dateien organisieren:
messengers.go: Erstellen Sie drei nicht verwandte Structs, die zufällig dasselbe Verhalten teilen:Phonemit einemNumber-Feld – seineSendMessage(text string) string-Methode gibtSMS to [Number]: [text]zurückComputermit einemEmail-Feld – seineSendMessage(text string) string-Methode gibtEmail to [Email]: [text]zurückPigeonmit einemName-Feld – seineSendMessage(text string) string-Methode gibt[Name] carries: [text]zurück
sender.go: Definieren Sie einMessenger-Interface, das eineSendMessage(text string) string-Methode erfordert. Erstellen Sie eine Funktion namensBroadcast, die einen Slice vonMessengerund einen Nachrichten-String entgegennimmt und dann einen Slice von Strings zurückgibt, der das Ergebnis des Aufrufs vonSendMessagefür jeden Messenger enthält.main.go: Lesen Sie die Details aus der Eingabe ein, erstellen Sie jeweils einen der Messenger-Typen, sammeln Sie diese in einem Slice vonMessengerund verwenden SieBroadcast, um eine Nachricht über alle zu senden. Geben Sie jedes Ergebnis in einer eigenen Zeile aus.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Telefonnummer
- Zeile 2: E-Mail-Adresse
- Zeile 3: Name der Taube
- Zeile 4: Zu sendende Nachricht
Zum Beispiel, gegeben 555-1234, alice@mail.com, Winston und Hello World, sollte Ihre Ausgabe wie folgt aussehen:
SMS to 555-1234: Hello World
Email to alice@mail.com: Hello World
Winston carries: Hello WorldDie entscheidende Erkenntnis hierbei ist, dass Phone, Computer und Pigeon nichts gemeinsam haben – sie teilen keinen Basistyp und implementieren Messenger nicht explizit. Da sie jedoch alle auf die gleiche Weise „quaken“ (die passende Methode haben), erlaubt Go, dass sie alle als Messenger-Werte verwendet werden. Das ist Duck-Typing in der Praxis.
Spickzettel
In Go bedeutet Duck-Typing, dass ein Typ nicht explizit deklarieren muss, dass er ein Interface implementiert – er muss lediglich über die erforderlichen Methoden mit den entsprechenden Signaturen verfügen.
Ein Typ erfüllt ein Interface implizit, wenn er alle vom Interface geforderten Methoden besitzt:
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!" }
Sowohl Duck und Robot erfüllen das Quacker-Interface, ohne es in ihren Definitionen zu erwähnen:
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quack!
MakeItQuack(Robot{}) // Beep-quack!
}
Diese implizite Erfüllung ermöglicht es Ihnen, Interfaces zu definieren, nachdem Typen bereits existieren, und sogar Interfaces zu erstellen, die zu Typen aus externen Paketen passen, ohne deren Quellcode zu ändern. Der Compiler prüft zur Kompilierzeit, ob Typen über die erforderlichen Methoden verfügen, was Typsicherheit bietet und gleichzeitig Flexibilität bewahrt.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Telefonnummer lesen
scanner.Scan()
phoneNumber := scanner.Text()
// E-Mail-Adresse lesen
scanner.Scan()
email := scanner.Text()
// Taubenname lesen
scanner.Scan()
pigeonName := scanner.Text()
// Zu sendende Nachricht lesen
scanner.Scan()
message := scanner.Text()
// TODO: Erstelle jeweils einen Messenger-Typ (Phone, Computer, Pigeon)
// TODO: Sammle alle Messenger in einem Slice vom Typ Messenger
// TODO: Verwende Broadcast, um die Nachricht über alle Messenger zu senden
// TODO: Gib jedes Ergebnis in einer eigenen Zeile aus
// Platzhalter zur Verwendung von Variablen (beim Implementieren entfernen)
_ = phoneNumber
_ = email
_ = pigeonName
_ = message
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der Go OOP
Externe DateienGo Workspace & ModulePackages & ImportsExportierte vs. nicht-exportierte NamenEinführung in OOP in GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktor-FunktionenZusammenfassung – Einfacher Taschenrechner4Interfaces
Einführung in InterfacesImplizite ImplementierungInterface als VertragLeeres Interface (any)Type AssertionType SwitchInterface-KompositionStringer & Error InterfacesRückblick - Formen-Rechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf Paket-EbeneGetter- & Setter-MethodenInformation Hiding in GoZusammenfassung - Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterType ConstraintsGenerische StructsWorkaround für generische MethodenZusammenfassung – Generische Collection2Vertiefung: Typen & Structs
Basis- & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct TagsAnonyme StructsVerschachtelte StructsZero Values & StandardwerteZusammenfassung - Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen des Struct EmbeddingMethod PromotionEinbetten mehrerer StructsEmbedding vs. AggregationShadowing eingebetteter MethodenZusammenfassung – Mitarbeiter-Hierarchie8Fehlerbehandlung & OOP
Das error-InterfaceEigene FehlertypenError Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverZusammenfassung – Datei-Parser3Zeiger & Speicher
Zeiger-Grundlagen in GoZeiger auf StructsWertübergabe vs. ReferenzübergabeDie new() FunktionGarbage Collection in GoWiederholung – Linked List Builder6Polymorphismus in Go
Polymorphismus über InterfacesDuck Typing in GoInterface-ErfüllungsregelnPolymorphe CollectionsDependency InjectionZusammenfassung – Payment Processor9Nebenläufigkeit & OOP
Goroutines GrundlagenChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignZusammenfassung - Worker Pool