Menu
Coddy logo textTech

Regeln zur Interface-Erfüllung

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 Schnittstellen verwendet, gibt es bestimmte Regeln, die bestimmen, ob ein Typ tatsächlich eine Schnittstelle erfüllt. Wenn du diese Regeln verstehst, kannst du subtile Fehler 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 {  // Zeigerempfänger
    return "Saved: " + d.Name
}

func Process(s Saver) {
    fmt.Println(s.Save())
}

Da Save() einen Pointer-Empfänger hat, erfüllt nur *Document die Anforderungen von Saver:

func main() {
    doc := Document{Name: "report.txt"}
    
    Process(&doc)  // funktioniert - Zeiger erfüllt Interface
    // Process(doc)  // Kompilierfehler - Wert erfüllt Interface nicht
}

Die umgekehrte Richtung ist jedoch flexibler. Wenn eine Methode einen Wertempfänger hat, erfüllen sowohl der Werttyp als auch der Zeigertyp das Interface. Go dereferenziert Zeiger automatisch, wenn Methoden mit Wertempfänger aufgerufen werden:

func (d Document) Info() string {  // Wert-Receiver
    return d.Name
}

// Sowohl Document als auch *Document erfüllen ein Interface, das Info() erfordert

Diese Asymmetrie besteht, weil Go immer einen Wert von einem Zeiger erhalten kann (durch Dereferenzierung), aber nicht immer einen Zeiger aus einem Wert erhalten kann (der Wert ist möglicherweise nicht adressierbar). Wenn du diese Regel im Hinterkopf behältst, verhinderst du verwirrende Compilerfehler bei der Arbeit mit Interfaces.

challenge icon

Aufgabe

Einfach

Erstellen wir ein Konfigurationssystem, das zeigt, wie sich pointer- und value-Receiver auf die Erfüllung eines interface auswirken. Du erstellst Typen, bei denen die Wahl des Receivers bestimmt, ob Werte, pointer oder beides mit einem interface verwendet werden können.

Du organisierst deinen Code über drei Dateien:

  • config.go: Define ein Configurable-interface, das zwei methods erfordert: GetValue() string und SetValue(string). Erstelle anschließend zwei Konfigurationstypen:
    • ReadOnlyConfig mit einem Value-field: Verwende 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-field: Verwende value-Receiver für both methods (GetValue gibt Data zurück, SetValue gibt einfach "Cannot modify" aus, ohne etwas zu ändern).
  • processor.go: Erstelle eine function namens ProcessConfig, die ein Configurable und einen neuen value-String accepts. Sie soll den current value mit GetValue() ausgeben, SetValue() mit dem neuen value aufrufen und anschließend den value erneut ausgeben, um any changes zu zeigen.
  • main.go: Lies Konfigurationsdetails aus der Eingabe und veranschauliche die Regeln für die Erfüllung des interface:
    1. Erstelle ein ReadOnlyConfig und übergib einen pointer an ProcessConfig (erforderlich, because SetValue einen pointer-Receiver hat).
    2. Erstelle ein Setting und übergib den value direkt an ProcessConfig (funktioniert, because both methods value-Receiver haben).

Die folgenden Eingaben werden bereitgestellt:

  • Line 1: Initialer value für ReadOnlyConfig
  • Line 2: Neuer value, der für ReadOnlyConfig gesetzt werden soll
  • Line 3: Initialer value für Setting
  • Line 4: Neuer value, der für Setting versucht werden soll

Deine ProcessConfig-function sollte in diesem format ausgeben:

Current: [value]
Current: [value after SetValue]

Wenn beispielsweise debug, production, localhost und remote angegeben werden, sollte deine Ausgabe folgendermaßen aussehen:

Current: debug
Current: production
Current: localhost
Cannot modify
Current: localhost

Beachte, wie ReadOnlyConfig seinen value tatsächlich ändert (because wir einen pointer übergeben haben), während Setting unverändert bleibt (sein SetValue mit einem value-Receiver kann den ursprünglichen value nicht modify). Die wichtigste Erkenntnis ist, dass ReadOnlyConfig-Werte allein Configurable nicht erfüllen würden, sondern nur pointer, während Setting-Werte direkt funktionieren, because alle methods von Setting value-Receiver verwenden.

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 ein ReadOnlyConfig mit dem Wert rocInitial
	// TODO: Übergebe einen POINTER an ProcessConfig (erforderlich, weil SetValue einen Pointer-Receiver hat)
	
	// TODO: Erstelle ein Setting mit dem Wert settingInitial
	// TODO: Übergebe den VALUE direkt an ProcessConfig (funktioniert, weil beide Methoden Value-Receiver haben)
	
	// Verwende diese Variablen, um Fehler durch ungenutzte 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

Übe selbstständig: Online-Go-Compiler