Polymorphismus über Interfaces
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 41 von 107.
Polymorphismus ermöglicht es, verschiedene Typen über eine gemeinsame Schnittstelle einheitlich zu behandeln. In Go wird dies ausschließlich durch Interfaces erreicht, ohne Vererbung oder Klassenhierarchien.
Wenn eine Funktion einen Interface-Typ als Parameter akzeptiert, kann jeder konkrete Typ, der dieses Interface implementiert, übergeben werden. Die Funktion muss den spezifischen Typ nicht kennen – sie interessiert sich nur für das durch das Interface definierte Verhalten:
type Speaker interface {
Speak() string
}
type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof!" }
type Cat struct{ Name string }
func (c Cat) Speak() string { return "Meow!" }
func MakeSound(s Speaker) {
fmt.Println(s.Speak())
}
Jetzt funktioniert MakeSound mit jedem Typ, der eine Speak()-Methode hat:
func main() {
dog := Dog{Name: "Rex"}
cat := Cat{Name: "Whiskers"}
MakeSound(dog) // Wuff!
MakeSound(cat) // Miau!
}
Derselbe Funktionsaufruf erzeugt unterschiedliches Verhalten, abhängig vom tatsächlich übergebenen Typ. Dies ist Polymorphismus in Aktion. Die Funktion MakeSound wird einmal geschrieben, funktioniert aber mit einer unbegrenzten Anzahl von Typen, solange diese das Interface Speaker erfüllen.
Dieser Ansatz hält Ihren Code flexibel und erweiterbar. Das Hinzufügen eines neuen Typs, der spricht, erfordert keine Änderungen an bestehenden Funktionen – implementieren Sie einfach das Interface, und es funktioniert automatisch.
Aufgabe
EinfachLassen Sie uns ein Fahrzeugbeschreibungssystem erstellen, das Polymorphismus in Aktion demonstriert. Sie werden verschiedene Fahrzeugtypen erstellen, die alle ein gemeinsames Verhalten über ein Interface teilen, und dann eine einzelne Funktion schreiben, die mit jedem Fahrzeug funktioniert.
Sie werden Ihren Code auf zwei Dateien aufteilen:
vehicles.go: Definieren Sie einDescriberInterface, das eineDescribe() stringMethode erfordert. Erstellen Sie dann drei Fahrzeugtypen, die dieses Interface jeweils auf ihre eigene Weise implementieren:Carmit den FeldernBrandundModel— dessenDescribe()gibtCar: [Brand] [Model]zurückMotorcyclemit den FeldernBrandundEngineCC(int) — dessenDescribe()gibtMotorcycle: [Brand] [EngineCC]cczurückBicyclemit dem FeldType(wie "Mountain" oder "Road") — dessenDescribe()gibtBicycle: [Type]zurück
main.go: Erstellen Sie eine Funktion namensPrintDescription, die jedenDescriberakzeptiert und das Ergebnis des Aufrufs vonDescribe()ausgibt. Lesen Sie Fahrzeugdetails aus der Eingabe ein, erstellen Sie von jedem Fahrzeugtyp eines und übergeben Sie jedes anPrintDescription, um zu demonstrieren, dass dieselbe Funktion mit allen drei verschiedenen Typen funktioniert.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Car brand
- Zeile 2: Car model
- Zeile 3: Motorcycle brand
- Zeile 4: Motorcycle engine CC (Integer)
- Zeile 5: Bicycle type
Zum Beispiel, gegeben Toyota, Camry, Honda, 600 und Mountain, sollte Ihre Ausgabe wie folgt aussehen:
Car: Toyota Camry
Motorcycle: Honda 600cc
Bicycle: MountainBeachten Sie, dass PrintDescription nicht wissen muss, ob es ein Car, Motorcycle oder Bicycle erhält — es ruft einfach Describe() auf und jeder Typ antwortet mit seiner eigenen, einzigartigen Ausgabe. Das ist Polymorphismus: eine Funktion, mehrere Verhaltensweisen.
Spickzettel
Polymorphismus ermöglicht es, verschiedene Typen durch eine gemeinsame Schnittstelle einheitlich zu behandeln. In Go wird dies durch Interfaces ohne Vererbung oder Klassenhierarchien erreicht.
Wenn eine Funktion einen Interface-Typ als Parameter akzeptiert, kann jeder konkrete Typ, der dieses Interface implementiert, übergeben werden:
type Speaker interface {
Speak() string
}
type Dog struct{ Name string }
func (d Dog) Speak() string { return "Woof!" }
type Cat struct{ Name string }
func (c Cat) Speak() string { return "Meow!" }
func MakeSound(s Speaker) {
fmt.Println(s.Speak())
}
Dieselbe Funktion arbeitet mit verschiedenen Typen:
func main() {
dog := Dog{Name: "Rex"}
cat := Cat{Name: "Whiskers"}
MakeSound(dog) // Woof!
MakeSound(cat) // Meow!
}
Die Funktion wird einmal geschrieben, funktioniert aber mit unbegrenzten Typen, solange sie das Interface erfüllen. Das Hinzufügen neuer Typen erfordert keine Änderungen an bestehenden Funktionen – implementieren Sie einfach das Interface.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// TODO: Erstelle eine Funktion namens PrintDescription, die einen beliebigen Describer akzeptiert
// und das Ergebnis des Aufrufs von Describe() ausgibt
func main() {
reader := bufio.NewReader(os.Stdin)
// Autodetails lesen
carBrand, _ := reader.ReadString('\n')
carBrand = strings.TrimSpace(carBrand)
carModel, _ := reader.ReadString('\n')
carModel = strings.TrimSpace(carModel)
// Motorraddetails lesen
motoBrand, _ := reader.ReadString('\n')
motoBrand = strings.TrimSpace(motoBrand)
motoEngineStr, _ := reader.ReadString('\n')
motoEngineStr = strings.TrimSpace(motoEngineStr)
motoEngine, _ := strconv.Atoi(motoEngineStr)
// Fahrraddetails lesen
bicycleType, _ := reader.ReadString('\n')
bicycleType = strings.TrimSpace(bicycleType)
// TODO: Erstelle ein Car, Motorcycle und Bicycle unter Verwendung der Eingabewerte
// TODO: Rufe PrintDescription für jedes Fahrzeug auf, um Polymorphismus zu demonstrieren
fmt.Println("TODO: Print vehicle descriptions")
}
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