Workaround für generische Methoden
Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy. Lektion 71 von 107.
Go hat eine bemerkenswerte Einschränkung: Du kannst keine Methoden mit eigenen Typparametern für eine Struktur definieren. Obwohl generische Strukturen hervorragend funktionieren, ist das Hinzufügen einer Methode, die einen neuen Typparameter einführt, nicht erlaubt.
Dieser Code lässt sich nicht kompilieren:
type Box[T any] struct {
Value T
}
// ERROR: Methode darf keine Typparameter haben
func (b Box[T]) Convert[U any](fn func(T) U) U {
return fn(b.Value)
}Die Problemumgehung besteht darin, eine eigenständige generische Funktion anstelle einer Methode zu verwenden:
type Box[T any] struct {
Value T
}
// Reguläre Methode - verwendet den Typparameter der Struktur
func (b Box[T]) Get() T {
return b.Value
}
// Eigenständige Funktion - kann eigene Typparameter haben
func Convert[T, U any](b Box[T], fn func(T) U) U {
return fn(b.Value)
}
func main() {
intBox := Box[int]{Value: 42}
// Verwende die Methode
fmt.Println(intBox.Get()) // 42
// Verwende die eigenständige Funktion
strResult := Convert(intBox, func(n int) string {
return fmt.Sprintf("Number: %d", n)
})
fmt.Println(strResult) // Number: 42
}Methoden für generische Strukturen können weiterhin den Typparameter T der Struktur verwenden. Die Einschränkung gilt nur für das Einführen zusätzlicher Typparameter in der Methodensignatur.
Wenn du diese Flexibilität benötigst, erzielt eine generische Funktion, die die Struktur als ihr erstes Argument übernimmt, dasselbe Ergebnis.
Aufgabe
EinfachErstelle ein Toolkit zur Datentransformation, das die Umgehung der Einschränkung von Go bei generischen Methoden demonstriert! Da Methoden keine neuen Typparameter einführen können, erstellst du eigenständige generische Funktionen, die dieselbe Flexibilität bieten.
Du organisierst deinen Code auf zwei Dateien verteilt:
wrapper.go: Definiere deinen generischen Container und die Transformationsfunktionen.Erstelle eine generische Struktur
Wrapper[T any]mit einem einzigen FeldValuevom TypT.Füge
Geteine MethodeWrapper[T]hinzu, die den verpackten Wert zurückgibt. Diese Methode verwendet den Typparameter der Struktur, was zulässig ist.Erstelle eine eigenständige generische Funktion
Transform[T, U any](w Wrapper[T], fn func(T) U) U, die die Transformationsfunktion auf den Wert des Wrappers anwendet und das Ergebnis zurückgibt. Diese Funktion benötigt einen eigenen TypparameterUfür den Ausgabetyp. Deshalb muss sie eine eigenständige Funktion und keine Methode sein.Erstelle eine weitere eigenständige Funktion
TransformToString[T any](w Wrapper[T]) string, die den verpackten Wert mithilfe vonfmt.Sprintf("%v", ...)in eine Zeichenfolge umwandelt.main.go: Demonstriere das Umgehungsmuster mit verschiedenen Transformationen.Lies einen Typindikator (
intoderstring) und anschließend einen Wert ein. Erstelle einenWrapperdes entsprechenden Typs.Bei der Eingabe
int: Erstelle einenWrapper[int], verwende anschließendTransform, um den Wert zu verdoppeln (wobei ein int zurückgegeben wird), und verwendeTransformToString, um eine Zeichenfolgendarstellung zu erhalten.Bei der Eingabe
string: Erstelle einenWrapper[string], verwende anschließendTransform, um die Länge der Zeichenfolge zu ermitteln (wobei ein int zurückgegeben wird), und verwendeTransformToString, um die Zeichenfolgendarstellung zu erhalten.Gib die Ergebnisse in diesem Format aus:
Original: [value] Transformed: [transformed value] As String: [string representation]
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Typindikator (
intoderstring) - Zeile 2: Der Wert
Beispielsweise bei:
int
25deine Ausgabe sollte folgendermaßen aussehen:
Original: 25
Transformed: 50
As String: 25Und bei:
string
Hello Worlddeine Ausgabe sollte folgendermaßen aussehen:
Original: Hello World
Transformed: 11
As String: Hello WorldDie wichtigste Erkenntnis ist, dass Transform einen Wrapper[T] als erstes Argument entgegennimmt und einen neuen Typparameter U für den Rückgabetyp einführt. Das wäre als Methode der Struktur nicht möglich. Dieses Muster bietet dir die Flexibilität generischer Transformationen und berücksichtigt gleichzeitig die Einschränkungen des Typsystems von Go.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Typindikator lesen
typeIndicator, _ := reader.ReadString('\n')
typeIndicator = strings.TrimSpace(typeIndicator)
// Wert lesen
value, _ := reader.ReadString('\n')
value = strings.TrimSpace(value)
if typeIndicator == "int" {
// Den Integer-Wert parsen
num, _ := strconv.Atoi(value)
// TODO: Erstelle einen Wrapper[int] mit der geparsten Zahl
// TODO: Verwende Transform, um den Wert zu verdoppeln (Hinweis: übergebe eine Funktion, die verdoppelt)
// TODO: Verwende TransformToString, um die String-Darstellung zu erhalten
// TODO: Gib die Ergebnisse im erforderlichen Format aus:
// Original: [value]
// Transformed: [transformed value]
// As String: [string representation]
_ = num // Entferne diese Zeile, wenn du num verwendest
} else if typeIndicator == "string" {
// TODO: Erstelle einen Wrapper[string] mit dem Wert
// TODO: Verwende Transform, um die Länge des Strings zu erhalten (Hinweis: übergib eine Funktion, die len() zurückgibt)
// TODO: Verwende TransformToString, um die String-Darstellung zu erhalten
// TODO: Gib die Ergebnisse im erforderlichen Format aus:
// Original: [value]
// Transformed: [transformed value]
// As String: [string representation]
_ = value // Entferne diese Zeile, wenn du value verwendest
}
}
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