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 vollständig durch Interfaces erreicht, ohne Vererbung oder Klassenhierarchien.
Wenn eine Funktion einen Schnittstellentyp als Parameter akzeptiert, kann jeder konkrete Typ übergeben werden, der diese Schnittstelle implementiert. Die Funktion muss den spezifischen Typ nicht kennen. Sie berücksichtigt nur das von der Schnittstelle 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 über eine Speak()-Methode verfügt:
func main() {
dog := Dog{Name: "Rex"}
cat := Cat{Name: "Whiskers"}
MakeSound(dog) // Wuff!
MakeSound(cat) // Miau!
}
Der gleiche Funktionsaufruf erzeugt je nach tatsächlich übergebenem Typ ein unterschiedliches Verhalten. Das ist Polymorphismus in Aktion. Die Funktion MakeSound wird einmal geschrieben, funktioniert aber mit einer unbegrenzten Anzahl von Typen, solange diese das Speaker-Interface erfüllen.
Dieser Ansatz hält deinen Code flexibel und erweiterbar. Das Hinzufügen eines neuen Typs, der spricht, erfordert keine Änderungen an vorhandenen Funktionen. Implementiere einfach die Schnittstelle, und es funktioniert automatisch.
Aufgabe
EinfachErstellen wir ein Fahrzeugbeschreibungssystem, das Polymorphie in Aktion demonstriert. Du wirst verschiedene Fahrzeugtypen erstellen, die über ein Interface ein gemeinsames Verhalten teilen, und anschließend eine einzelne Funktion schreiben, die mit jedem Fahrzeug funktioniert.
Du wirst deinen Code auf zwei Dateien verteilen:
vehicles.go: Definiere einDescriber-Interface, das eineDescribe() string-Methode erfordert. Erstelle anschließend drei Fahrzeugtypen, die dieses Interface jeweils auf ihre eigene Weise implementieren:Carmit den FeldernBrandundModel. Die MethodeDescribe()gibtCar: [Brand] [Model]zurückMotorcyclemit den FeldernBrandundEngineCC(int). Die MethodeDescribe()gibtMotorcycle: [Brand] [EngineCC]cczurückBicyclemit dem FeldType(zum Beispiel "Mountain" oder "Road"). Die MethodeDescribe()gibtBicycle: [Type]zurück
main.go: Erstelle eine Funktion namensPrintDescription, die jedesDescriberakzeptiert und das Ergebnis des Aufrufs vonDescribe()ausgibt. Lies die Fahrzeugdetails aus der Eingabe ein, erstelle jeweils ein Fahrzeug jedes Typs und übergib jedes davon anPrintDescription, um zu demonstrieren, dass dieselbe Funktion mit allen drei verschiedenen Typen funktioniert.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Automarke
- Zeile 2: Automodell
- Zeile 3: Motorradmarke
- Zeile 4: Motorrad-Motorhubraum in CC (Ganzzahl)
- Zeile 5: Fahrradtyp
Wenn beispielsweise Toyota, Camry, Honda, 600 und Mountain gegeben sind, sollte deine Ausgabe folgendermaßen aussehen:
Car: Toyota Camry
Motorcycle: Honda 600cc
Bicycle: MountainBeachte, 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 eindeutigen Ausgabe. Das ist Polymorphie: eine Funktion, mehrere Verhaltensweisen.
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 mit den Eingabewerten
// 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 OOP in Go
Externe DateienGo-Workspace & ModulePackages & ImportsExportierte vs. nicht exportierte NamenEinführung in OOP mit GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktorfunktionenRückblick – Einfacher Taschenrechner4Schnittstellen
Einführung in SchnittstellenImplizite ImplementierungSchnittstelle als VertragLeere Schnittstelle (any)TypzusicherungTypwechselZusammensetzung von SchnittstellenStringer- und Error-SchnittstellenRückblick – Formenrechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf PaketebeneGetter- und Setter-MethodenInformationsverbergung in GoRückblick – Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterTypbeschränkungenGenerische StrukturenWorkaround für generische MethodenZusammenfassung – Generische Sammlung2Typen & Structs im Detail
Grundlegende & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct-TagsAnonyme StructsVerschachtelte StructsNullwerte & StandardwerteRückblick – Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen der Struct-EinbettungMethoden-PromotionMehrere Structs einbettenEinbettung vs. AggregationVerbergen eingebetteter MethodenRückblick – Mitarbeiterhierarchie8Fehlerbehandlung & OOP
Das Error-InterfaceBenutzerdefinierte FehlertypenError-Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverRückblick – Dateiparser3Zeiger & Speicher
Grundlagen von Zeigern in GoZeiger auf StructsÜbergabe per Wert vs. ReferenzDie Funktion new()Garbage Collection in GoRückblick – Verkettete Liste erstellen6Polymorphismus in Go
Polymorphismus über InterfacesDuck-Typing in GoRegeln zur Interface-ErfüllungPolymorphe SammlungenDependency InjectionZusammenfassung – Zahlungsprozessor9Konkurrenz & OOP
Grundlagen der GoroutinesChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignRückblick – Worker PoolÜbe selbstständig: Online-Go-Compiler