Polymorphe Sammlungen
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 44 von 107.
Eine der leistungsfähigsten Anwendungen von Schnittstellen ist das Speichern verschiedener Typen in derselben Sammlung. Ein Slice eines Schnittstellentyps kann jeden Wert aufnehmen, der diese Schnittstelle erfüllt, sodass du verwandte, aber unterschiedliche Typen zusammen gruppieren kannst.
Stell dir ein Szenario vor, in dem du verschiedene Formen verwalten musst. Statt separate Slices für jeden Typ zu verwenden, kannst du eine einzige Slice des Interface-Typs verwenden:
type Shape interface {
Area() float64
}
type Circle struct{ Radius float64 }
func (c Circle) Area() float64 { return 3.14159 * c.Radius * c.Radius }
type Rectangle struct{ Width, Height float64 }
func (r Rectangle) Area() float64 { return r.Width * r.Height }
func main() {
shapes := []Shape{
Circle{Radius: 5},
Rectangle{Width: 4, Height: 3},
Circle{Radius: 2},
}
for _, s := range shapes {
fmt.Printf("Area: %.2f\n", s.Area())
}
}
Das Slice shapes enthält sowohl Kreise als auch Rechtecke. Beim Iterieren reagiert jedes Element entsprechend seinem tatsächlichen Typ auf Area(). Das ist Polymorphismus, angewendet auf Sammlungen: Dieselbe Schleife verarbeitet alle Formtypen einheitlich.
Polymorphe Sammlungen sind beim Erstellen von Systemen, die unterschiedliche Elemente verarbeiten, unverzichtbar: etwa ein Benachrichtigungssystem, das E-Mails und SMS-Nachrichten versendet, ein Spiel, das verschiedene Gegnertypen aktualisiert, oder ein Dokumentprozessor, der mehrere Dateiformate verarbeitet. Die Sammlung kümmert sich nicht um bestimmte Typen, sondern nur darum, dass jedes Element das erforderliche Verhalten bereitstellt.
Aufgabe
EinfachWir erstellen ein Aufgabenverwaltungssystem, das verschiedene Aufgabentypen über eine einheitliche Schnittstelle verarbeitet. Du erstellst verschiedene Aufgabentypen und verarbeitest sie gemeinsam in einer polymorphen Sammlung.
Du organisierst deinen Code auf drei Dateien verteilt:
task.go: Definiere eineTask-Schnittstelle, die eineSummary() string-Methode voraussetzt. Diese Schnittstelle bildet den gemeinsamen Vertrag, den alle Aufgabentypen erfüllen müssen.types.go: Erstelle drei verschiedene Aufgabentypen, die jeweils dieTask-Schnittstelle implementieren:BugFixmit den FeldernID(string) undSeverity(string). Die MethodeSummary()gibtBug #[ID] ([Severity])zurück.Featuremit den FeldernName(string) undPoints(int). Die MethodeSummary()gibtFeature: [Name] - [Points] ptszurück.Documentationmit dem FeldTopic(string). Die MethodeSummary()gibtDocs: [Topic]zurück.
main.go: Erstelle eine Funktion namensPrintBacklog, die ein Slice vonTaskakzeptiert und die Zusammenfassung jeder Aufgabe in einer eigenen Zeile ausgibt. Lies die Aufgabendetails aus der Eingabe, erstelle jeweils eine Aufgabe jedes Typs, sammle sie in einem einzigen[]Task-Slice und übergib es anPrintBacklog.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Bug-ID
- Zeile 2: Schweregrad des Bugs
- Zeile 3: Name des Features
- Zeile 4: Feature-Punkte (Ganzzahl)
- Zeile 5: Dokumentationsthema
Wenn beispielsweise 1042, critical, Dark Mode, 8 und API Reference gegeben sind, sollte deine Ausgabe folgendermaßen aussehen:
Bug #1042 (critical)
Feature: Dark Mode - 8 pts
Docs: API ReferenceDie Stärke dieses Ansatzes liegt darin, dass PrintBacklog keine separate Logik für Bugs, Features oder Dokumentation benötigt. Die Funktion durchläuft einfach das Slice und ruft für jedes Element Summary() auf. Jeder Aufgabentyp reagiert mit seinem eigenen eindeutigen Format. So wird veranschaulicht, wie du mit polymorphen Sammlungen verschiedene Typen einheitlich verarbeiten kannst.
Probier es selbst
package main
import (
"fmt"
)
// TODO: Erstelle eine PrintBacklog-Funktion, die ein Slice von Task akzeptiert
// und die Zusammenfassung jeder Task in einer eigenen Zeile ausgibt
func main() {
// Eingabe lesen
var bugID string
var bugSeverity string
var featureName string
var featurePoints int
var docTopic string
fmt.Scanln(&bugID)
fmt.Scanln(&bugSeverity)
fmt.Scanln(&featureName)
fmt.Scanln(&featurePoints)
fmt.Scanln(&docTopic)
// TODO: Erstelle je einen Task von jedem Typ (BugFix, Feature, Documentation)
// TODO: Sammle alle Tasks in einem einzelnen []Task-Slice
// TODO: Rufe PrintBacklog mit dem Slice der Tasks auf
}
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