Menu
Coddy logo textTech

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.

challenge icon

Aufgabe

Einfach

Lassen 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 Feld Value vom Typ T.

    Fügen Sie eine Methode Get zu Wrapper[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 Typparameter U fü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 von fmt.Sprintf("%v", ...) in einen String umwandelt.

  • main.go: Demonstrieren Sie das Workaround-Muster mit verschiedenen Transformationen.

    Lesen Sie einen Typ-Indikator (int oder string) und anschließend einen Wert ein. Erstellen Sie einen Wrapper des entsprechenden Typs.

    Für int-Eingaben: Erstellen Sie einen Wrapper[int], verwenden Sie dann Transform, um den Wert zu verdoppeln (gibt einen int zurück), und verwenden Sie TransformToString, um eine String-Repräsentation zu erhalten.

    Für string-Eingaben: Erstellen Sie einen Wrapper[string], verwenden Sie dann Transform, um die Länge des Strings zu ermitteln (gibt einen int zurück), und verwenden Sie TransformToString, 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 (int oder string)
  • Zeile 2: Der Wert

Zum Beispiel bei:

int
25

Sollte Ihre Ausgabe wie folgt aussehen:

Original: 25
Transformed: 50
As String: 25

Und bei:

string
Hello World

Sollte Ihre Ausgabe wie folgt aussehen:

Original: Hello World
Transformed: 11
As String: Hello World

Die 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
	}
}
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