Polymorphe Collections
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 44 von 107.
Eine der leistungsfähigsten Anwendungen von Interfaces ist das Speichern verschiedener Typen in derselben Sammlung. Ein Slice eines Interface-Typs kann jeden Wert aufnehmen, der dieses Interface erfüllt, was es Ihnen ermöglicht, verwandte, aber unterschiedliche Typen zusammenzufassen.
Stellen Sie sich ein Szenario vor, in dem Sie verschiedene Formen verwalten müssen. Anstatt separater Slices für jeden Typ können Sie ein einzelnes 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())
}
}
Der shapes-Slice enthält sowohl Kreise als auch Rechtecke. Beim Iterieren reagiert jedes Element entsprechend seinem tatsächlichen Typ auf Area(). Dies ist Polymorphismus, angewendet auf Sammlungen – dieselbe Schleife verarbeitet alle Formtypen einheitlich.
Polymorphe Sammlungen sind essenziell beim Erstellen von Systemen, die verschiedenartige Elemente verarbeiten: ein Benachrichtigungssystem, das E-Mails und SMS-Nachrichten versendet, ein Spiel, das verschiedene Gegnertypen aktualisiert, oder ein Dokumentenprozessor, der mehrere Dateiformate verarbeitet. Der Sammlung sind die spezifischen Typen egal – wichtig ist nur, dass jedes Element das erforderliche Verhalten bereitstellt.
Aufgabe
EinfachLassen Sie uns ein Aufgabenverwaltungssystem erstellen, das verschiedene Arten von Aufgaben über eine einheitliche Schnittstelle verarbeitet. Sie werden verschiedene Aufgabentypen erstellen und diese alle zusammen in einer polymorphen Sammlung verarbeiten.
Sie werden Ihren Code über drei Dateien organisieren:
task.go: Definieren Sie einTaskInterface, das eineSummary() stringMethode erfordert. Dieses Interface wird der gemeinsame Vertrag sein, den alle Aufgabentypen erfüllen müssen.types.go: Erstellen Sie drei verschiedene Aufgabentypen, die jeweils dasTaskInterface implementieren:BugFixmit den FeldernID(string) undSeverity(string) — seineSummary()gibt"Bug #[ID] ([Severity])"zurückFeaturemit den FeldernName(string) undPoints(int) — seineSummary()gibt"Feature: [Name] - [Points] pts"zurückDocumentationmit dem FeldTopic(string) — seineSummary()gibt"Docs: [Topic]"zurück
main.go: Erstellen Sie eine Funktion namensPrintBacklog, die einen Slice vonTaskakzeptiert und die Zusammenfassung jeder Aufgabe in einer eigenen Zeile ausgibt. Lesen Sie die Aufgabendetails aus der Eingabe ein, erstellen Sie jeweils einen der Aufgabentypen, sammeln Sie alle in einem einzigen[]TaskSlice und übergeben Sie diesen anPrintBacklog.
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Bug ID
- Zeile 2: Bug severity
- Zeile 3: Feature name
- Zeile 4: Feature points (Ganzzahl)
- Zeile 5: Documentation topic
Zum Beispiel, gegeben "1042", "critical", "Dark Mode", 8 und "API Reference", sollte Ihre Ausgabe wie folgt aussehen:
Bug #1042 (critical)
Feature: Dark Mode - 8 pts
Docs: API ReferenceDie Stärke hierbei ist, dass PrintBacklog keine separate Logik für Bugs, Features oder Dokumentationen benötigt — sie iteriert einfach durch den Slice und ruft Summary() für jedes Element auf. Jeder Aufgabentyp antwortet mit seinem eigenen einzigartigen Format, was demonstriert, wie polymorphe Sammlungen es ermöglichen, verschiedene Typen einheitlich zu verarbeiten.
Spickzettel
Ein Slice eines Interface-Typs kann jeden Wert aufnehmen, der dieses Interface erfüllt, was polymorphe Sammlungen ermöglicht:
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 shapes-Slice enthält verschiedene Typen (Circle und Rectangle), die alle das Shape-Interface implementieren. Beim Iterieren reagiert jedes Element auf Area() entsprechend seinem tatsächlichen Typ – das ist Polymorphie angewendet auf Sammlungen.
Dieses Muster ist nützlich, um unterschiedliche Elemente einheitlich zu verarbeiten: Benachrichtigungssysteme, die verschiedene Nachrichtentypen verarbeiten, Spiele, die verschiedene Gegnertypen aktualisieren, oder Dokumentenprozessoren, die mehrere Dateiformate handhaben.
Probier es selbst
package main
import (
"fmt"
)
// TODO: Erstellen Sie eine PrintBacklog-Funktion, die einen Slice von Task akzeptiert
// und die Zusammenfassung jeder Aufgabe 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: Erstellen Sie jeweils einen Aufgabentyp (BugFix, Feature, Documentation)
// TODO: Sammeln Sie alle Aufgaben in einem einzigen []Task-Slice
// TODO: Rufen Sie PrintBacklog mit dem Slice der Aufgaben 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 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