Menu
Coddy logo textTech

Interface als Vertrag

Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 27 von 107.

Schnittstellen als Verträge zu betrachten, hilft Ihnen dabei, bessere Go-Programme zu entwerfen. Wenn eine Funktion einen Interface-Typ akzeptiert, besagt dies: „Es ist mir egal, welchen konkreten Typ Sie mir geben, solange er diesen Vertrag erfüllt.“

Dieses vertragsbasierte Denken ermöglicht es Ihnen, Funktionen zu schreiben, die sich eher auf das Verhalten als auf spezifische Typen konzentrieren. Betrachten Sie eine Funktion, die Daten aus verschiedenen Quellen verarbeiten muss:

type DataSource interface {
    FetchData() string
}

func ProcessData(src DataSource) {
    data := src.FetchData()
    fmt.Println("Processing:", data)
}

Die Funktion ProcessData weiß nicht und es ist ihr egal, ob sie eine Datenbankverbindung, einen API-Client oder einen Dateileser erhält. Sie setzt lediglich voraus, dass die Eingabe FetchData() ausführen kann. Jeder Typ, der diesen Vertrag erfüllt, funktioniert.

type Database struct{ ConnectionString string }
func (d Database) FetchData() string { return "data from DB" }

type APIClient struct{ Endpoint string }
func (a APIClient) FetchData() string { return "data from API" }

func main() {
    db := Database{ConnectionString: "localhost"}
    api := APIClient{Endpoint: "https://api.example.com"}
    
    ProcessData(db)   // Funktioniert!
    ProcessData(api)  // Funktioniert ebenfalls!
}

Dieser Vertragsansatz macht Ihren Code auch testbarer. Während des Testens können Sie einen Mock-Typ erstellen, der das Interface erfüllt, ohne echte Datenbanken oder Netzwerkverbindungen zu benötigen. Die zu testende Funktion erkennt den Unterschied nicht, da der Vertrag erfüllt ist.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Benachrichtigungssystem bauen, das demonstriert, wie Interfaces als Verträge fungieren. Sie werden ein Notifier-Interface erstellen, das definiert, was es bedeutet, eine Benachrichtigung zu senden, und dann mehrere Benachrichtigungskanäle implementieren, die alle diesen Vertrag erfüllen.

Sie werden Ihren Code auf zwei Dateien aufteilen:

  • notifiers.go: Definieren Sie ein Notifier-Interface mit einer einzigen Methode Notify(message string) string. Erstellen Sie dann drei verschiedene Structs, die diesen Vertrag erfüllen:
    • ConsoleNotifier mit einem Prefix-Feld
    • FileNotifier mit einem Filename-Feld
    • WebhookNotifier mit einem URL-Feld
    Jedes Struct sollte die Notify-Methode implementieren, um einen String zurückzugeben, der beschreibt, wie die Benachrichtigung gesendet werden würde.
  • main.go: Erstellen Sie eine Funktion namens BroadcastAlert, die einen Slice von Notifier-Werten und einen Nachrichten-String akzeptiert. Diese Funktion sollte Notify für jeden Notifier aufrufen und die Ergebnisse ausgeben. Lesen Sie die Konfiguration aus der Eingabe, erstellen Sie alle drei Notifier-Typen und senden Sie einen Alarm über alle aus.

Die folgenden Eingaben werden bereitgestellt:

  • Zeile 1: Konsolen-Präfix
  • Zeile 2: Dateiname
  • Zeile 3: Webhook-URL
  • Zeile 4: Alarmmeldung

Ihre Notify-Methoden sollten Strings in diesen Formaten zurückgeben:

  • ConsoleNotifier: [Prefix] [message]
  • FileNotifier: Writing to [Filename]: [message]
  • WebhookNotifier: POST [URL]: [message]

Zum Beispiel, gegeben ALERT:, log.txt, https://hooks.example.com und Server down!, sollte Ihre Ausgabe wie folgt aussehen:

ALERT: Server down!
Writing to log.txt: Server down!
POST https://hooks.example.com: Server down!

Die Stärke dieses Designs liegt darin, dass Ihre BroadcastAlert-Funktion die spezifischen Notifier-Typen nicht kennt oder sich nicht darum kümmert – sie erfordert nur, dass jeder den Notifier-Vertrag erfüllt. Sie könnten später einen SlackNotifier oder EmailNotifier hinzufügen, ohne die Broadcast-Funktion überhaupt ändern zu müssen.

Spickzettel

Interfaces in Go fungieren als Verträge, die erforderliches Verhalten definieren, ohne konkrete Typen festzulegen. Wenn eine Funktion einen Interface-Typ akzeptiert, kann jeder Typ verwendet werden, der die Methoden des Interfaces implementiert.

Definieren Sie ein Interface, indem Sie Methodensignaturen angeben:

type DataSource interface {
    FetchData() string
}

Funktionen, die Interfaces akzeptieren, arbeiten mit jedem Typ zusammen, der den Vertrag erfüllt:

func ProcessData(src DataSource) {
    data := src.FetchData()
    fmt.Println("Processing:", data)
}

Mehrere Typen können dasselbe Interface implementieren:

type Database struct{ ConnectionString string }
func (d Database) FetchData() string { return "data from DB" }

type APIClient struct{ Endpoint string }
func (a APIClient) FetchData() string { return "data from API" }

func main() {
    db := Database{ConnectionString: "localhost"}
    api := APIClient{Endpoint: "https://api.example.com"}
    
    ProcessData(db)   // Funktioniert!
    ProcessData(api)  // Funktioniert ebenfalls!
}

Dieser vertragsbasierte Ansatz ermöglicht:

  • Funktionen, die sich auf das Verhalten statt auf spezifische Typen konzentrieren
  • Bessere Testbarkeit durch Mock-Implementierungen
  • Erweiterbarkeit ohne Änderung des bestehenden Codes

Probier es selbst

package main

import (
	"bufio"
	"fmt"
	"os"
)

// BroadcastAlert sendet eine Nachricht über alle Notifier
// TODO: Implementiere diese Funktion, um:
// - Ein Slice von Notifier-Werten und einen message-String zu akzeptieren
// - Notify für jeden Notifier aufzurufen und die Ergebnisse auszugeben
func BroadcastAlert(notifiers []Notifier, message string) {
	// TODO: Durchlaufe die Notifier in einer Schleife und gib jedes Benachrichtigungsergebnis aus
}

func main() {
	scanner := bufio.NewScanner(os.Stdin)
	
	// Lese das Konsolen-Präfix
	scanner.Scan()
	prefix := scanner.Text()
	
	// Lese den Dateinamen
	scanner.Scan()
	filename := scanner.Text()
	
	// Lese die Webhook-URL
	scanner.Scan()
	url := scanner.Text()
	
	// Lese die Alarmmeldung
	scanner.Scan()
	message := scanner.Text()
	
	// TODO: Erstelle Instanzen aller drei Notifier-Typen
	// - ConsoleNotifier mit dem prefix
	// - FileNotifier mit dem filename
	// - WebhookNotifier mit der url
	
	// TODO: Erstelle ein Slice von Notifier, das alle drei Notifier enthält
	
	// TODO: Rufe BroadcastAlert mit den Notifiers und der message auf
	
	_ = prefix
	_ = filename
	_ = url
	_ = message
	fmt.Println("TODO: Implement the solution")
}
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