Implizite Implementierung
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 26 von 107.
In vielen objektorientierten Sprachen wie Java oder C# musst du ausdrücklich deklarieren, dass ein Typ ein Interface mithilfe von Schlüsselwörtern wie implements implementiert. Go verfolgt einen anderen Ansatz: Die Implementierung von Interfaces erfolgt implizit.
Ein Typ erfüllt eine Schnittstelle einfach dadurch, dass er alle ihre Methoden implementiert. Es ist nicht erforderlich, die Beziehung irgendwo zu deklarieren. Der Compiler erkennt sie automatisch.
type Writer interface {
Write(data string) int
}
type FileWriter struct {
Filename string
}
// FileWriter implementiert Writer implizit
func (f FileWriter) Write(data string) int {
fmt.Println("Writing to", f.Filename)
return len(data)
}
Beachte, dass es keine implements Writer-Deklaration bei FileWriter gibt. Da FileWriter über eine Write(data string) int-Methode verfügt, die mit der Signatur der Schnittstelle übereinstimmt, erfüllt es automatisch die Writer-Schnittstelle.
Dieses Design hat eine wichtige Konsequenz: Du kannst Schnittstellen definieren, nachdem Typen bereits existieren. Wenn eine Bibliothek eines Drittanbieters einen Typ mit den benötigten Methoden enthält, kannst du eine Schnittstelle erstellen, die dieser bereits erfüllt, ohne den ursprünglichen Code zu ändern.
func Save(w Writer, content string) {
w.Write(content)
}
func main() {
fw := FileWriter{Filename: "data.txt"}
Save(fw, "Hello!") // Funktioniert, weil FileWriter Writer erfüllt
}
Die implizite Implementierung hält Go-Code entkoppelt und flexibel. Typen müssen Schnittstellen nicht im Voraus kennen, wodurch es einfacher wird, Systeme aus unabhängigen Teilen zusammenzusetzen.
Aufgabe
EinfachErstellen wir ein Nachrichtenübermittlungssystem, das veranschaulicht, wie Typen in Go implizit Schnittstellen erfüllen. Du erstellst verschiedene Nachrichtentypen, die alle dieselbe Schnittstelle erfüllen, ohne diese Beziehung jemals explizit zu deklarieren.
Du organisierst deinen Code auf zwei Dateien:
messengers.go: Definiere eineMessenger-Schnittstelle mit einer einzigen MethodeSend(message string) string. Erstelle dann zwei Strukturen, die diese Schnittstelle implizit erfüllen:EmailMessengermit einemAddress-Feld (String)SMSMessengermit einemPhoneNumber-Feld (String)
Send-Methode besitzen, die einen String zurückgibt, der die Übermittlung beschreibt. Keine der beiden Strukturen sollte explizit deklarieren, dass sieMessengerimplementiert: Go erkennt das automatisch anhand der Methodensignaturen.main.go: Erstelle eine Funktion namensDeliverMessage, die einen beliebigenMessengerund einen Nachrichten-String akzeptiert und anschließend das Ergebnis des Aufrufs vonSendzurückgibt. Lies Kontaktinformationen aus der Eingabe, erstelle beide Nachrichtentypen und verwende deine Funktion, um über jeden von ihnen Nachrichten zu übermitteln.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: E-Mail-Adresse
- Zeile 2: Telefonnummer
- Zeile 3: Zu sendende Nachricht
Deine Send-Methoden sollten Strings in diesen Formaten zurückgeben:
- EmailMessenger:
Email to [Address]: [message] - SMSMessenger:
SMS to [PhoneNumber]: [message]
Wenn beispielsweise alice@example.com, 555-1234 und Hello! gegeben sind, sollte deine Ausgabe folgendermaßen aussehen:
Email to alice@example.com: Hello!
SMS to 555-1234: Hello!Die wichtigste Erkenntnis ist hier, dass deine DeliverMessage-Funktion jeden beliebigen Messenger akzeptiert und sowohl EmailMessenger als auch SMSMessenger diese Schnittstelle allein dadurch erfüllen, dass sie über eine passende Send-Methode verfügen. Es gibt nirgendwo ein implements-Schlüsselwort: Gos implizite Implementierung erledigt all das automatisch.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: Erstelle eine DeliverMessage-Funktion, die einen beliebigen Messenger akzeptiert
// und einen message-String, und dann das Ergebnis des Aufrufs von Send zurückgibt
func main() {
reader := bufio.NewReader(os.Stdin)
// Read email address
email, _ := reader.ReadString('\n')
email = email[:len(email)-1]
// Telefonnummer lesen
phone, _ := reader.ReadString('\n')
phone = phone[:len(phone)-1]
// Nachricht lesen
message, _ := reader.ReadString('\n')
if len(message) > 0 && message[len(message)-1] == '\n' {
message = message[:len(message)-1]
}
// TODO: Erstelle einen EmailMessenger mit der E-Mail-Adresse
// TODO: Erstelle einen SMSMessenger mit der Telefonnummer
// TODO: Verwende DeliverMessage, um die Nachricht über jeden Messenger zu senden
// und gib die Ergebnisse aus
}
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