Menu
Coddy logo textTech

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.

challenge icon

Aufgabe

Einfach

Lassen 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 ein Task Interface, das eine Summary() string Methode erfordert. Dieses Interface wird der gemeinsame Vertrag sein, den alle Aufgabentypen erfüllen müssen.
  • types.go: Erstellen Sie drei verschiedene Aufgabentypen, die jeweils das Task Interface implementieren:
    • BugFix mit den Feldern ID (string) und Severity (string) — seine Summary() gibt "Bug #[ID] ([Severity])" zurück
    • Feature mit den Feldern Name (string) und Points (int) — seine Summary() gibt "Feature: [Name] - [Points] pts" zurück
    • Documentation mit dem Feld Topic (string) — seine Summary() gibt "Docs: [Topic]" zurück
  • main.go: Erstellen Sie eine Funktion namens PrintBacklog, die einen Slice von Task akzeptiert 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 []Task Slice und übergeben Sie diesen an PrintBacklog.

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 Reference

Die 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
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung