Interface-Komposition
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 31 von 107.
Go ermöglicht es Ihnen, größere Interfaces durch das Komponieren kleinerer zu bauen. Anstatt ein einziges großes Interface mit vielen Methoden zu definieren, betten Sie bestehende Interfaces in eine neue Interface-Definition ein.
type Reader interface {
Read() string
}
type Writer interface {
Write(data string)
}
// ReadWriter kombiniert beide Schnittstellen
type ReadWriter interface {
Reader
Writer
}Das ReadWriter-Interface erfordert nun sowohl die Read()- als auch die Write()-Methode. Jeder Typ, der beide Methoden implementiert, erfüllt sowohl ReadWriter als auch Reader und Writer einzeln.
type File struct {
Name string
}
func (f File) Read() string { return "file content" }
func (f File) Write(data string) { fmt.Println("Writing:", data) }
func Process(rw ReadWriter) {
content := rw.Read()
rw.Write(content)
}
func main() {
f := File{Name: "data.txt"}
Process(f) // File erfüllt ReadWriter
}Dieser Kompositionsansatz hält Interfaces klein und fokussiert. Sie können sie nach Bedarf kombinieren und so präzise Verträge für Ihre Funktionen erstellen. Die Standardbibliothek von Go nutzt dieses Muster ausgiebig, wie zum Beispiel io.ReadWriter, das io.Reader und io.Writer kombiniert.
Aufgabe
EinfachLassen Sie uns ein Mediaplayer-System bauen, das die Komposition von Interfaces demonstriert. Sie werden kleine, fokussierte Interfaces erstellen und diese dann zu einem leistungsfähigeren, zusammengesetzten Interface kombinieren, das ein einzelner Typ erfüllen kann.
Sie werden Ihren Code in zwei Dateien organisieren:
media.go: Definieren Sie drei Interfaces und ein Struct, das sie alle implementiert:- Ein
Player-Interface mit einerPlay() string-Methode - Ein
Recorder-Interface mit einerRecord(content string) string-Methode - Ein
MediaDevice-Interface, das sowohlPlayerals auchRecorderzusammensetzt - Ein
SmartDevice-Struct mit einemName-Feld, das alle erforderlichen Methoden implementiert
- Ein
main.go: Erstellen Sie Funktionen, die demonstrieren, wie dasselbeSmartDeviceüber verschiedene Interface-Typen verwendet werden kann. Schreiben Sie drei Funktionen:UsePlayer, die einenPlayerakzeptiert und dessenPlay-Methode aufruftUseRecorder, die einenRecorderund einen Inhalts-String akzeptiert und dannRecordaufruftUseMediaDevice, die einMediaDeviceund einen Inhalts-String akzeptiert und dann sowohlRecordals auchPlayaufruft
SmartDeviceund demonstrieren Sie dessen Verwendung über alle drei Interface-Typen.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Gerätename
- Zeile 2: Aufzuzeichnender Inhalt
Ihre Methoden sollten Strings in diesen Formaten zurückgeben:
Play():[Name] is playingRecord(content):[Name] recorded: [content]
Zum Beispiel, gegeben MyPhone und voice memo, sollte Ihre Ausgabe wie folgt aussehen:
MyPhone is playing
MyPhone recorded: voice memo
MyPhone recorded: voice memo
MyPhone is playingDie ersten beiden Zeilen zeigen das Gerät, das über die einzelnen Interfaces (Player und Recorder) verwendet wird. Die letzten beiden Zeilen zeigen die Verwendung über das zusammengesetzte MediaDevice-Interface, das beide Fähigkeiten erfordert. Beachten Sie, wie Ihr einzelnes SmartDevice alle drei Interfaces erfüllt, da es beide erforderlichen Methoden besitzt.
Spickzettel
Go ermöglicht es Ihnen, größere Interfaces zu erstellen, indem Sie kleinere komponieren, indem Sie bestehende Interfaces in eine neue Interface-Definition einbetten:
type Reader interface {
Read() string
}
type Writer interface {
Write(data string)
}
// ReadWriter combines both interfaces
type ReadWriter interface {
Reader
Writer
}Jeder Typ, der alle Methoden der eingebetteten Interfaces implementiert, erfüllt das komponierte Interface:
type File struct {
Name string
}
func (f File) Read() string { return "file content" }
func (f File) Write(data string) { fmt.Println("Writing:", data) }
func Process(rw ReadWriter) {
content := rw.Read()
rw.Write(content)
}
func main() {
f := File{Name: "data.txt"}
Process(f) // File satisfies ReadWriter
}Ein Typ, der ein komponiertes Interface implementiert, erfüllt auch jedes der einzelnen eingebetteten Interfaces. Dieser Kompositionsansatz hält Interfaces klein und fokussiert, während er es Ihnen ermöglicht, präzise Verträge für Ihre Funktionen zu erstellen.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
)
// TODO: Implementiere UsePlayer, das einen Player akzeptiert und dessen Play-Methode aufruft
// Es sollte das Ergebnis von Play() ausgeben
// TODO: Implementiere UseRecorder, das einen Recorder und einen Inhalts-String akzeptiert
// Es sollte das Ergebnis von Record(content) ausgeben
// TODO: Implementiere UseMediaDevice, das ein MediaDevice und einen Inhalts-String akzeptiert
// Es sollte das Ergebnis von Record(content) und dann Play() ausgeben
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
deviceName := scanner.Text()
scanner.Scan()
content := scanner.Text()
// TODO: Erstelle ein SmartDevice mit dem angegebenen Namen
// TODO: Verwende das Gerät über das Player-Interface
// TODO: Verwende das Gerät über das Recorder-Interface
// TODO: Verwende das Gerät über das MediaDevice-Interface
_ = deviceName
_ = content
fmt.Println("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