Menu
Coddy logo textTech

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.

challenge icon

Aufgabe

Einfach

Lassen 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 ein Configurable Interface, das zwei Methoden erfordert: GetValue() string und SetValue(string). Erstellen Sie dann zwei Konfigurationstypen:
    • ReadOnlyConfig mit einem Value Feld — verwenden Sie einen Value-Receiver für GetValue() (gibt den Value zurück) und einen Pointer-Receiver für SetValue() (aktualisiert den Value)
    • Setting mit einem Data Feld — 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 namens ProcessConfig, die ein Configurable und einen neuen Wert-String akzeptiert. Sie sollte den aktuellen Wert mit GetValue() 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:
    1. Erstellen Sie eine ReadOnlyConfig und übergeben Sie einen Pointer an ProcessConfig (erforderlich, da SetValue einen Pointer-Receiver hat)
    2. Erstellen Sie ein Setting und übergeben Sie den Wert direkt an ProcessConfig (funktioniert, da beide Methoden Value-Receiver haben)

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: localhost

Beachten 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() erfordert

Warum 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
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung