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.
Aufgabe
EinfachLassen 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 einNotifier-Interface mit einer einzigen MethodeNotify(message string) string. Erstellen Sie dann drei verschiedene Structs, die diesen Vertrag erfüllen:ConsoleNotifiermit einemPrefix-FeldFileNotifiermit einemFilename-FeldWebhookNotifiermit einemURL-Feld
Notify-Methode implementieren, um einen String zurückzugeben, der beschreibt, wie die Benachrichtigung gesendet werden würde.main.go: Erstellen Sie eine Funktion namensBroadcastAlert, die einen Slice vonNotifier-Werten und einen Nachrichten-String akzeptiert. Diese Funktion sollteNotifyfü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")
}
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