Duck-Typing in Go
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 42 von 107.
Der Begriff Duck-Typing stammt aus dem Sprichwort: „Wenn es wie eine Ente läuft und wie eine Ente quakt, dann ist es eine Ente.“ In Go bedeutet das, dass ein Typ nicht ausdrücklich deklarieren muss, dass er ein Interface implementiert. Er muss lediglich über die richtigen Methoden verfügen.
Betrachte diese Schnittstelle und zwei völlig 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ähnt Quacker irgendwo in ihren Definitionen. Dennoch erfüllen beide die Schnittstelle, einfach weil sie über eine Quack()-Methode mit der korrekten Signatur verfügen:
func MakeItQuack(q Quacker) {
fmt.Println(q.Quack())
}
func main() {
MakeItQuack(Duck{}) // Quak!
MakeItQuack(Robot{}) // Piep-Quak!
}
Diese implizite Erfüllung ist leistungsstark, weil sie es dir ermöglicht, Schnittstellen zu definieren, nachdem die Typen bereits existieren. Du kannst eine Schnittstelle erstellen, die Typen aus externen Paketen entspricht, ohne deren Quellcode zu ändern. Die Typen müssen nichts von deiner Schnittstelle wissen. Sie müssen sich nur korrekt verhalten.
Duck-Typing in Go bietet dir die Flexibilität dynamischer Sprachen und bewahrt gleichzeitig die Typsicherheit zur Compile-Zeit. 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
EinfachErstellen wir ein Nachrichtensystem, das Duck-Typing in der Praxis demonstriert. Du erstellst vollständig unabhängige Typen, die alle Nachrichten über eine gemeinsame Schnittstelle senden können, ohne dass einer von ihnen ausdrücklich angibt, diese zu implementieren.
Du organisierst deinen Code über drei Dateien:
messengers.go: Erstelle drei unabhängige Structs, die zufällig dasselbe Verhalten aufweisen:Phonemit einem FeldNumber. Seine MethodeSendMessage(text string) stringgibtSMS to [Number]: [text]zurückComputermit einem FeldEmail. Seine MethodeSendMessage(text string) stringgibtEmail to [Email]: [text]zurückPigeonmit einem FeldName. Seine MethodeSendMessage(text string) stringgibt[Name] carries: [text]zurück
sender.go: Definiere eineMessenger-Schnittstelle, die eineSendMessage(text string) string-Methode erfordert. Erstelle eine Funktion namensBroadcast, die ein Slice vonMessengerund einen Nachrichten-String entgegennimmt und anschließend ein Slice von Strings zurückgibt, das die Ergebnisse des Aufrufs vonSendMessagefür jeden Messenger enthält.main.go: Lies die Details aus der Eingabe ein, erstelle jeweils eine Instanz jedes Messenger-Typs, fasse sie in einem Slice vonMessengerzusammen und verwendeBroadcast, um eine Nachricht über alle zu senden. Gib 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 verbreitende Nachricht
Wenn beispielsweise 555-1234, alice@mail.com, Winston und Hello World angegeben werden, sollte deine Ausgabe wie folgt aussehen:
SMS to 555-1234: Hello World
Email to alice@mail.com: Hello World
Winston carries: Hello WorldDie wichtigste Erkenntnis hierbei ist, dass Phone, Computer und Pigeon nichts gemeinsam haben. Sie teilen keinen Basistyp und implementieren Messenger nicht ausdrücklich. Da sie sich jedoch alle auf dieselbe Weise verhalten (über die passende Methode verfügen), erlaubt Go, sie alle als Messenger-Werte zu verwenden. Das ist Duck-Typing in der Praxis.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Telefonnummer lesen
scanner.Scan()
phoneNumber := scanner.Text()
// Read email address
scanner.Scan()
email := scanner.Text()
// Taubennamen lesen
scanner.Scan()
pigeonName := scanner.Text()
// Zu sendende Nachricht lesen
scanner.Scan()
message := scanner.Text()
// TODO: Erstelle einen von jedem Messenger-Typ (Phone, Computer, Pigeon)
// TODO: Sammle alle Messenger in einem Slice von Messenger
// TODO: Verwende Broadcast, um die Nachricht über alle Messenger zu senden
// TODO: Gib jedes Ergebnis in einer eigenen Zeile aus
// Platzhalter zur Verwendung der 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 OOP in Go
Externe DateienGo-Workspace & ModulePackages & ImportsExportierte vs. nicht exportierte NamenEinführung in OOP mit GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktorfunktionenRückblick – Einfacher Taschenrechner4Schnittstellen
Einführung in SchnittstellenImplizite ImplementierungSchnittstelle als VertragLeere Schnittstelle (any)TypzusicherungTypwechselZusammensetzung von SchnittstellenStringer- und Error-SchnittstellenRückblick – Formenrechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf PaketebeneGetter- und Setter-MethodenInformationsverbergung in GoRückblick – Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterTypbeschränkungenGenerische StrukturenWorkaround für generische MethodenZusammenfassung – Generische Sammlung2Typen & Structs im Detail
Grundlegende & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct-TagsAnonyme StructsVerschachtelte StructsNullwerte & StandardwerteRückblick – Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen der Struct-EinbettungMethoden-PromotionMehrere Structs einbettenEinbettung vs. AggregationVerbergen eingebetteter MethodenRückblick – Mitarbeiterhierarchie8Fehlerbehandlung & OOP
Das Error-InterfaceBenutzerdefinierte FehlertypenError-Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverRückblick – Dateiparser3Zeiger & Speicher
Grundlagen von Zeigern in GoZeiger auf StructsÜbergabe per Wert vs. ReferenzDie Funktion new()Garbage Collection in GoRückblick – Verkettete Liste erstellen6Polymorphismus in Go
Polymorphismus über InterfacesDuck-Typing in GoRegeln zur Interface-ErfüllungPolymorphe SammlungenDependency InjectionZusammenfassung – Zahlungsprozessor9Konkurrenz & OOP
Grundlagen der GoroutinesChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignRückblick – Worker PoolÜbe selbstständig: Online-Go-Compiler