Solución alternativa para métodos genéricos
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy. Lección 71 de 107.
Go tiene una limitación notable: no puedes definir métodos con sus propios parámetros de tipo en una estructura. Aunque las estructuras genéricas funcionan muy bien, no se permite añadir un método que introduzca un parámetro de tipo nuevo.
Este código no compilará:
type Box[T any] struct {
Value T
}
// ERROR: el método no debe tener parámetros de tipo
func (b Box[T]) Convert[U any](fn func(T) U) U {
return fn(b.Value)
}La solución alternativa es usar una función genérica independiente en lugar de un método:
type Box[T any] struct {
Value T
}
// Método regular: usa el parámetro de tipo de la struct
func (b Box[T]) Get() T {
return b.Value
}
// Función independiente: puede tener sus propios parámetros de tipo
func Convert[T, U any](b Box[T], fn func(T) U) U {
return fn(b.Value)
}
func main() {
intBox := Box[int]{Value: 42}
// Usar el método
fmt.Println(intBox.Get()) // 42
// Usar la función independiente
strResult := Convert(intBox, func(n int) string {
return fmt.Sprintf("Number: %d", n)
})
fmt.Println(strResult) // Number: 42
}Los métodos de las estructuras genéricas aún pueden usar el parámetro de tipo de la estructura T. La restricción solo se aplica a introducir parámetros de tipo adicionales en la firma del método.
Cuando necesitas esa flexibilidad, una función genérica que toma la estructura como su primer argumento logra el mismo resultado.
Desafío
Fácil¡Construyamos un kit de herramientas de transformación de datos que demuestre la solución alternativa para la limitación de Go con los métodos genéricos! Como los métodos no pueden introducir nuevos parámetros de tipo, crearás funciones genéricas independientes que logran la misma flexibilidad.
Organizarás tu código en dos archivos:
wrapper.go: Define tu contenedor genérico y las funciones de transformación.Crea una estructura genérica
Wrapper[T any]con un único campoValuede tipoT.Añade un método
GetaWrapper[T]que devuelva el valor envuelto. Este método utiliza el parámetro de tipo de la estructura, lo cual está permitido.Crea una función genérica independiente
Transform[T, U any](w Wrapper[T], fn func(T) U) Uque aplique la función de transformación al valor del contenedor y devuelva el resultado. Esta función necesita su propio parámetro de tipoUpara el tipo de salida, por lo que debe ser una función independiente en lugar de un método.Crea otra función independiente
TransformToString[T any](w Wrapper[T]) stringque convierta el valor envuelto en una cadena usandofmt.Sprintf("%v", ...).main.go: Demuestra el patrón de solución alternativa con diferentes transformaciones.Lee un indicador de tipo (
intostring) y después lee un valor. Crea unWrapperdel tipo correspondiente.Para una entrada
int: crea unWrapper[int], después usaTransformpara duplicar el valor (devolviendo un int) y usaTransformToStringpara obtener una representación en forma de cadena.Para una entrada
string: crea unWrapper[string], después usaTransformpara obtener la longitud de la cadena (devolviendo un int) y usaTransformToStringpara obtener la representación en forma de cadena.Imprime los resultados con este formato:
Original: [value] Transformed: [transformed value] As String: [string representation]
Se proporcionarán las siguientes entradas:
- Línea 1: Indicador de tipo (
intostring) - Línea 2: El valor
Por ejemplo, dada la siguiente entrada:
int
25Tu salida debería ser:
Original: 25
Transformed: 50
As String: 25Y dada la siguiente entrada:
string
Hello WorldTu salida debería ser:
Original: Hello World
Transformed: 11
As String: Hello WorldLa idea clave es que Transform recibe un Wrapper[T] como primer argumento e introduce un nuevo parámetro de tipo U para el tipo de retorno. Algo que no sería posible como método de la estructura. Este patrón te proporciona la flexibilidad de las transformaciones genéricas mientras trabajas dentro de las limitaciones del sistema de tipos de Go.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Lee el indicador de tipo
typeIndicator, _ := reader.ReadString('\n')
typeIndicator = strings.TrimSpace(typeIndicator)
// Lee el valor
value, _ := reader.ReadString('\n')
value = strings.TrimSpace(value)
if typeIndicator == "int" {
// Parsea el valor entero
num, _ := strconv.Atoi(value)
// TODO: Crea un Wrapper[int] con el número parseado
// TODO: Usa Transform para duplicar el valor (pista: pasa una función que duplique)
// TODO: Usa TransformToString para obtener la representación en string
// TODO: Imprime los resultados en el formato requerido:
// Original: [value]
// Transformed: [transformed value]
// As String: [string representation]
_ = num // Elimina esta línea cuando uses num
} else if typeIndicator == "string" {
// TODO: Crea un Wrapper[string] con el valor
// TODO: Usa Transform para obtener la longitud del string (pista: pasa una función que devuelva len())
// TODO: Usa TransformToString para obtener la representación en string
// TODO: Imprime los resultados en el formato requerido:
// Original: [value]
// Transformed: [transformed value]
// As String: [string representation]
_ = value // Elimina esta línea cuando uses value
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de POO en Go
Archivos externosEspacio de trabajo y módulos de GoPaquetes e importacionesNombres exportados vs. no exportadosIntroducción a la POO en GoStructs como clasesDefinición de métodos en structsReceptores de puntero vs. de valorInicialización de structsFunciones constructorasResumen - Calculadora simple4Interfaces
Introducción a las interfacesImplementación implícitaLa interfaz como contratoInterfaz vacía (any)Type AssertionType SwitchComposición de interfacesInterfaces Stringer y ErrorResumen: Calculadora de figuras7Encapsulamiento
Campos exportados vs. no exportadosEncapsulamiento a nivel de paqueteMétodos Getter y SetterOcultamiento de información en GoResumen - Registros de estudiantes10Genéricos (Go 1.18+)
Introducción a los GenéricosParámetros de tipoRestricciones de tipoStructs genéricosSolución alternativa para métodos genéricosResumen - Colección genérica13Patrones de diseño - Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón Abstract FactoryPatrón ObserverPatrón Strategy2Inmersión profunda en Tipos y Structs
Tipos básicos y compuestosDefiniciones de tipos personalizadosEtiquetas de StructsStructs anónimosStructs anidadosValores cero y por defectoRepaso - Libreta de contactos5Composición sobre herencia
Por qué Go no tiene herenciaConceptos básicos de incrustación de structsPromoción de métodosIncrustación de múltiples structsIncrustación vs AgregaciónShadowing de métodos incrustadosResumen - Jerarquía de empleados8Manejo de Errores y OOP
La interfaz errorTipos de errores personalizadosEnvoltura de errores (fmt.Errorf)Errores centinelaerrors.Is() y errors.As()Panic, Defer y RecoverResumen - Analizador de archivos3Punteros y Memoria
Fundamentos de punteros en GoPunteros a StructsPaso por valor vs referenciaLa función new()Garbage Collection en GoResumen - Constructor de Linked List6Polimorfismo en Go
Polimorfismo mediante interfacesDuck Typing en GoReglas de satisfacción de interfacesColecciones polimórficasInyección de dependenciasResumen - Procesador de pagos9Concurrencia y POO
Fundamentos de GoroutinesChannels y comunicaciónChannels con buffer vs. sin bufferSentencia Selectsync.Mutex y sync.RWMutexsync.WaitGroupDiseño de estructuras Thread-SafeResumen - Worker PoolPractica por tu cuenta: Compilador de Go online