Implizite Implementierung
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 26 von 107.
In vielen objektorientierten Sprachen wie Java oder C# müssen Sie explizit deklarieren, dass ein Typ ein Interface implementiert, indem Sie Schlüsselwörter wie implements verwenden. Go verfolgt einen anderen Ansatz: Die Interface-Implementierung erfolgt implizit.
Ein Typ erfüllt ein Interface einfach dadurch, dass er alle seine Methoden implementiert. Es ist nicht erforderlich, die Beziehung irgendwo zu deklarieren. Der Compiler findet dies automatisch heraus.
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)
}
Beachten Sie, dass es keine implements Writer-Deklaration bei FileWriter gibt. Da FileWriter über eine Write(data string) int-Methode verfügt, die der Interface-Signatur entspricht, erfüllt es automatisch das Writer-Interface.
Dieses Design hat eine weitreichende Konsequenz: Sie können Interfaces definieren, nachdem Typen bereits existieren. Wenn eine Bibliothek eines Drittanbieters einen Typ mit Methoden hat, die Sie benötigen, können Sie ein Interface erstellen, das 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, da FileWriter Writer erfüllt
}
Implizite Implementierung hält Go-Code entkoppelt und flexibel. Typen müssen Interfaces nicht im Voraus kennen, was es einfacher macht, Systeme aus unabhängigen Teilen zusammenzusetzen.
Aufgabe
EinfachLassen Sie uns ein Nachrichtenübermittlungssystem bauen, das demonstriert, wie Typen in Go Schnittstellen implizit erfüllen. Sie werden verschiedene Messenger-Typen erstellen, die alle dieselbe Schnittstelle erfüllen – ohne diese Beziehung jemals explizit zu deklarieren.
Sie werden Ihren Code auf zwei Dateien aufteilen:
messengers.go: Definieren Sie einMessenger-Interface mit einer einzigen MethodeSend(message string) string. Erstellen Sie dann zwei Structs, die dieses Interface implizit erfüllen werden:EmailMessengermit einemAddress-Feld (string)SMSMessengermit einemPhoneNumber-Feld (string)
Send-Methode haben, die einen String zurückgibt, der die Zustellung beschreibt. Keines der Structs sollte explizit deklarieren, dass esMessengerimplementiert – Go wird das basierend auf den Methodensignaturen automatisch erkennen.main.go: Erstellen Sie eine Funktion namensDeliverMessage, die einen beliebigenMessengerund einen Nachrichten-String akzeptiert und dann das Ergebnis des Aufrufs vonSendzurückgibt. Lesen Sie Kontaktinformationen aus der Eingabe ein, erstellen Sie beide Messenger-Typen und verwenden Sie Ihre Funktion, um Nachrichten über jeden einzelnen zuzustellen.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: E-Mail-Adresse
- Zeile 2: Telefonnummer
- Zeile 3: Zu sendende Nachricht
Ihre Send-Methoden sollten Strings in diesen Formaten zurückgeben:
- EmailMessenger:
Email to [Address]: [message] - SMSMessenger:
SMS to [PhoneNumber]: [message]
Zum Beispiel, gegeben alice@example.com, 555-1234 und Hello!, sollte Ihre Ausgabe wie folgt aussehen:
Email to alice@example.com: Hello!
SMS to 555-1234: Hello!Die entscheidende Erkenntnis hierbei ist, dass Ihre DeliverMessage-Funktion jeden Messenger akzeptiert und sowohl EmailMessenger als auch SMSMessenger dieses Interface erfüllen, einfach indem sie eine passende Send-Methode besitzen. Es gibt nirgendwo ein implements-Schlüsselwort – Gos implizite Implementierung kümmert sich um alles.
Spickzettel
In Go erfolgt die Implementierung von Interfaces implizit. Ein Typ erfüllt ein Interface einfach dadurch, dass er alle seine Methoden implementiert – es ist keine explizite Deklaration erforderlich.
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)
}
Es gibt keine implements Writer-Deklaration. Da FileWriter über eine Write(data string) int-Methode verfügt, die der Interface-Signatur entspricht, erfüllt er automatisch das Writer-Interface.
Sie können jeden Typ verwenden, der ein Interface erfüllt, ohne dass der Typ das Interface kennen muss:
func Save(w Writer, content string) {
w.Write(content)
}
func main() {
fw := FileWriter{Filename: "data.txt"}
Save(fw, "Hello!") // Funktioniert, da FileWriter das Writer-Interface erfüllt
}
Dieses Design ermöglicht es Ihnen, Interfaces zu definieren, nachdem Typen bereits existieren, wodurch der Code entkoppelt und flexibel wird.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: Erstellen Sie eine DeliverMessage-Funktion, die einen beliebigen Messenger
// und einen Nachrichten-String akzeptiert und dann das Ergebnis des Send-Aufrufs zurückgibt
func main() {
reader := bufio.NewReader(os.Stdin)
// E-Mail-Adresse lesen
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: Erstellen Sie einen EmailMessenger mit der E-Mail-Adresse
// TODO: Erstellen Sie einen SMSMessenger mit der Telefonnummer
// TODO: Verwenden Sie DeliverMessage, um die Nachricht über jeden Messenger zu senden
// und die Ergebnisse auszugeben
}
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