Interface-Erfüllungsregeln
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 43 von 107.
Während Go Duck-Typing für die implizite Erfüllung von Interfaces verwendet, gibt es spezifische Regeln, die bestimmen, ob ein Typ tatsächlich ein Interface erfüllt. Das Verständnis dieser Regeln hilft Ihnen, subtile Fehler zu vermeiden.
Die wichtigste Regel betrifft Pointer-Receiver. Wenn eine Methode mit einem Pointer-Receiver definiert ist, erfüllt nur ein Pointer auf diesen Typ das Interface – nicht der Wert selbst:
type Saver interface {
Save() string
}
type Document struct{ Name string }
func (d *Document) Save() string { // Pointer-Receiver
return "Saved: " + d.Name
}
func Process(s Saver) {
fmt.Println(s.Save())
}
Da Save() einen Pointer-Receiver hat, erfüllt nur *Document das Interface Saver:
func main() {
doc := Document{Name: "report.txt"}
Process(&doc) // funktioniert - Pointer erfüllt das Interface
// Process(doc) // Kompilierfehler - Wert erfüllt das Interface nicht
}
Umgekehrt ist es jedoch flexibler. Wenn eine Methode einen Value-Receiver hat, erfüllen sowohl der Wert- als auch der Zeigertyp das Interface. Go dereferenziert Zeiger automatisch beim Aufruf von Methoden mit Value-Receiver:
func (d Document) Info() string { // Value-Receiver
return d.Name
}
// Sowohl Document als auch *Document erfüllen ein Interface, das Info() erfordert
Diese Asymmetrie besteht, da Go immer einen Wert von einem Pointer erhalten kann (durch Dereferenzierung), aber nicht immer einen Pointer von einem Wert erhalten kann (der Wert ist möglicherweise nicht adressierbar). Wenn man diese Regel im Hinterkopf behält, verhindert dies verwirrende Compiler-Fehler bei der Arbeit mit Interfaces.
Aufgabe
EinfachLassen Sie uns ein Konfigurationssystem erstellen, das demonstriert, wie sich Pointer- und Value-Receiver auf die Erfüllung von Interfaces auswirken. Sie werden Typen erstellen, bei denen die Wahl des Receivers bestimmt, ob Werte, Pointer oder beides mit einem Interface verwendet werden können.
Sie werden Ihren Code über drei Dateien organisieren:
config.go: Definieren Sie einConfigurableInterface, das zwei Methoden erfordert:GetValue() stringundSetValue(string). Erstellen Sie dann zwei Konfigurationstypen:ReadOnlyConfigmit einemValueFeld — verwenden Sie einen Value-Receiver fürGetValue()(gibt den Value zurück) und einen Pointer-Receiver fürSetValue()(aktualisiert den Value)Settingmit einemDataFeld — verwenden Sie Value-Receiver für beide Methoden (GetValue gibt Data zurück, SetValue gibt einfach "Cannot modify" aus, ohne etwas zu ändern)
processor.go: Erstellen Sie eine Funktion namensProcessConfig, die einConfigurableund einen neuen Wert-String akzeptiert. Sie sollte den aktuellen Wert mitGetValue()ausgeben,SetValue()mit dem neuen Wert aufrufen und dann den Wert erneut ausgeben, um etwaige Änderungen zu zeigen.main.go: Lesen Sie Konfigurationsdetails aus der Eingabe und demonstrieren Sie die Regeln zur Interface-Erfüllung:- Erstellen Sie eine
ReadOnlyConfigund übergeben Sie einen Pointer anProcessConfig(erforderlich, daSetValueeinen Pointer-Receiver hat) - Erstellen Sie ein
Settingund übergeben Sie den Wert direkt anProcessConfig(funktioniert, da beide Methoden Value-Receiver haben)
- Erstellen Sie eine
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Initialer Wert für ReadOnlyConfig
- Zeile 2: Neuer Wert, der für ReadOnlyConfig gesetzt werden soll
- Zeile 3: Initialer Wert für Setting
- Zeile 4: Neuer Wert, der für Setting versucht werden soll
Ihre ProcessConfig Funktion sollte in diesem Format ausgeben:
Current: [value]
Current: [value after SetValue]Zum Beispiel, gegeben debug, production, localhost und remote, sollte Ihre Ausgabe wie folgt aussehen:
Current: debug
Current: production
Current: localhost
Cannot modify
Current: localhostBeachten Sie, wie ReadOnlyConfig tatsächlich seinen Wert ändert (da wir einen Pointer übergeben haben), während Setting unverändert bleibt (sein SetValue mit einem Value-Receiver kann das Original nicht modifizieren). Die entscheidende Erkenntnis ist, dass ReadOnlyConfig Werte allein Configurable nicht erfüllen würden — nur Pointer tun dies —, während Setting Werte direkt funktionieren, da alle seine Methoden Value-Receiver verwenden.
Spickzettel
Go verwendet Duck-Typing für die implizite Erfüllung von Interfaces, aber es gibt spezifische Regeln für Pointer- und Value-Receiver:
Pointer-Receiver-Regel
Wenn eine Methode einen Pointer-Receiver hat, erfüllt nur ein Pointer auf diesen Typ das Interface:
type Saver interface {
Save() string
}
type Document struct{ Name string }
func (d *Document) Save() string { // Pointer-Receiver
return "Saved: " + d.Name
}
func main() {
doc := Document{Name: "report.txt"}
Process(&doc) // funktioniert - Pointer erfüllt das Interface
// Process(doc) // Kompilierfehler - Wert erfüllt das Interface nicht
}Value-Receiver-Regel
Wenn eine Methode einen Value-Receiver hat, erfüllen sowohl der Wert- als auch der Pointer-Typ das Interface:
func (d Document) Info() string { // Value-Receiver
return d.Name
}
// Sowohl Document als auch *Document erfüllen ein Interface, das Info() erfordertWarum diese Asymmetrie existiert
Go kann immer einen Wert aus einem Pointer erhalten (durch Dereferenzierung), aber nicht immer einen Pointer aus einem Wert (der Wert ist möglicherweise nicht adressierbar).
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Eingaben lesen
scanner.Scan()
rocInitial := scanner.Text()
scanner.Scan()
rocNew := scanner.Text()
scanner.Scan()
settingInitial := scanner.Text()
scanner.Scan()
settingNew := scanner.Text()
// TODO: Erstelle eine ReadOnlyConfig mit dem Wert rocInitial
// TODO: Übergib einen POINTER an ProcessConfig (erforderlich, da SetValue einen pointer receiver hat)
// TODO: Erstelle ein Setting mit dem Wert settingInitial
// TODO: Übergib den VALUE direkt an ProcessConfig (funktioniert, da beide Methoden value receivers haben)
// Verwende diese Variablen, um Fehler wegen nicht verwendeter Variablen zu vermeiden
_ = rocInitial
_ = rocNew
_ = settingInitial
_ = settingNew
}
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