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: Man kann keine Methoden mit eigenen Typparametern für ein Struct definieren. Während generische Structs hervorragend funktionieren, ist das Hinzufügen einer Methode, die einen neuen Typparameter einführt, nicht zulässig.
Dieser Code lässt sich nicht kompilieren:
type Box[T any] struct {
Value T
}
// FEHLER: Methode darf keine Typparameter haben
func (b Box[T]) Convert[U any](fn func(T) U) U {
return fn(b.Value)
}Der Workaround 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 des Structs
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}
// Die Methode verwenden
fmt.Println(intBox.Get()) // 42
// Die eigenständige Funktion verwenden
strResult := Convert(intBox, func(n int) string {
return fmt.Sprintf("Number: %d", n)
})
fmt.Println(strResult) // Number: 42
}Methoden für generische Structs können weiterhin den Typparameter T des Structs verwenden. Die Einschränkung gilt nur für das Einführen zusätzlicher Typparameter in der Methodensignatur.
Wenn Sie diese Flexibilität benötigen, erzielt eine generische Funktion, die das struct als erstes Argument entgegennimmt, dasselbe Ergebnis.
Aufgabe
EinfachLassen Sie uns ein Toolkit zur Datentransformation erstellen, das den Workaround für die Einschränkung von Go bei generischen Methoden demonstriert! Da Methoden keine neuen Typparameter einführen können, werden Sie eigenständige generische Funktionen erstellen, die dieselbe Flexibilität erreichen.
Sie werden Ihren Code auf zwei Dateien verteilen:
wrapper.go: Definieren Sie Ihren generischen Container und die Transformationsfunktionen.Erstellen Sie ein generisches Struct
Wrapper[T any]mit einem einzigen FeldValuevom TypT.Fügen Sie eine Methode
GetzuWrapper[T]hinzu, die den eingepackten Wert zurückgibt. Diese Methode verwendet den Typparameter des Structs, was zulässig ist.Erstellen Sie 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 ihren eigenen TypparameterUfür den Ausgabetyp, weshalb sie eine eigenständige Funktion und keine Methode sein muss.Erstellen Sie eine weitere eigenständige Funktion
TransformToString[T any](w Wrapper[T]) string, die den eingepackten Wert unter Verwendung vonfmt.Sprintf("%v", ...)in einen String umwandelt.main.go: Demonstrieren Sie das Workaround-Muster mit verschiedenen Transformationen.Lesen Sie einen Typ-Indikator (
intoderstring) und anschließend einen Wert ein. Erstellen Sie einenWrapperdes entsprechenden Typs.Für
int-Eingaben: Erstellen Sie einenWrapper[int], verwenden Sie dannTransform, um den Wert zu verdoppeln (gibt einen int zurück), und verwenden SieTransformToString, um eine String-Repräsentation zu erhalten.Für
string-Eingaben: Erstellen Sie einenWrapper[string], verwenden Sie dannTransform, um die Länge des Strings zu ermitteln (gibt einen int zurück), und verwenden SieTransformToString, um die String-Repräsentation zu erhalten.Geben Sie die Ergebnisse in diesem Format aus:
Original: [value] Transformed: [transformed value] As String: [string representation]
Die folgenden Eingaben werden bereitgestellt:
- Zeile 1: Typ-Indikator (
intoderstring) - Zeile 2: Der Wert
Zum Beispiel bei:
int
25Sollte Ihre Ausgabe wie folgt aussehen:
Original: 25
Transformed: 50
As String: 25Und bei:
string
Hello WorldSollte Ihre Ausgabe wie folgt aussehen:
Original: Hello World
Transformed: 11
As String: Hello WorldDie entscheidende Erkenntnis ist, dass Transform einen Wrapper[T] als erstes Argument entgegennimmt und einen neuen Typparameter U für den Rückgabetyp einführt – etwas, das als Methode auf dem Struct nicht möglich wäre. Dieses Muster bietet Ihnen die Flexibilität generischer Transformationen, während Sie innerhalb der Einschränkungen des Typsystems von Go arbeiten.
Spickzettel
Go erlaubt es Methoden auf generischen Structs nicht, eigene Typparameter einzuführen. Methoden können nur die bereits vorhandenen Typparameter des Structs verwenden.
Dieser Code wird nicht kompiliert:
type Box[T any] struct {
Value T
}
// ERROR: method must have no type parameters
func (b Box[T]) Convert[U any](fn func(T) U) U {
return fn(b.Value)
}Der Workaround besteht darin, eigenständige generische Funktionen anstelle von Methoden zu verwenden:
type Box[T any] struct {
Value T
}
// Regular method - uses the struct's type parameter
func (b Box[T]) Get() T {
return b.Value
}
// Standalone function - can have its own type parameters
func Convert[T, U any](b Box[T], fn func(T) U) U {
return fn(b.Value)
}
func main() {
intBox := Box[int]{Value: 42}
// Use the method
fmt.Println(intBox.Get()) // 42
// Use the standalone function
strResult := Convert(intBox, func(n int) string {
return fmt.Sprintf("Number: %d", n)
})
fmt.Println(strResult) // Number: 42
}Methoden auf generischen Structs können den Typparameter des Structs verwenden. Die Einschränkung gilt nur für die Einführung zusätzlicher Typparameter in der Methodensignatur. Wenn Sie diese Flexibilität benötigen, verwenden Sie eine generische Funktion, die den Struct als erstes Argument entgegennimmt.
Probier es selbst
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Typ-Indikator 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: Übergib eine Funktion, die verdoppelt)
// TODO: Verwende TransformToString, um die String-Repräsentation zu erhalten
// TODO: Ergebnisse im erforderlichen Format ausgeben:
// 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-Repräsentation zu erhalten
// TODO: Ergebnisse im erforderlichen Format ausgeben:
// 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 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