Structs genéricos
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 70 de 107.
Al igual que las funciones, los structs también pueden tener parámetros de tipo. Un struct genérico te permite crear estructuras de datos que funcionan con cualquier tipo mientras se mantiene la seguridad de tipos. Esto es perfecto para construir contenedores reutilizables como pilas, colas o pares clave-valor.
Para definir una estructura genérica, coloca el parámetro de tipo después del nombre de la estructura:
type Box[T any] struct {
Value T
}
func main() {
intBox := Box[int]{Value: 42}
strBox := Box[string]{Value: "hello"}
fmt.Println(intBox.Value) // 42
fmt.Println(strBox.Value) // hello
}A diferencia de las funciones genéricas, debes especificar explícitamente el tipo al crear una instancia de un struct genérico. Go no puede inferir el tipo solo a partir del contexto.
Las estructuras genéricas pueden tener múltiples parámetros de tipo, lo que las hace ideales para estructuras como pares o mapas:
type Pair[K, V any] struct {
Key K
Value V
}
p := Pair[string, int]{Key: "age", Value: 25}
fmt.Printf("%s: %d\n", p.Key, p.Value) // age: 25También puedes aplicar restricciones para asegurar que los tipos almacenados admitan operaciones específicas:
type NumberBox[T int | float64] struct {
Value T
}
nb := NumberBox[float64]{Value: 3.14}Las estructuras genéricas forman la base para construir estructuras de datos reutilizables y con seguridad de tipos en Go. En la próxima lección, aprenderás a definir métodos en estos tipos genéricos.
Desafío
Fácil¡Vamos a construir un sistema de inventario genérico que pueda rastrear diferentes tipos de artículos! Crearás estructuras de contenedores reutilizables que funcionen con cualquier tipo manteniendo una seguridad de tipos completa.
Organizarás tu código en dos archivos:
inventory.go: Define tus estructuras de contenedores genéricos.Crea una estructura genérica
Item[T any]con dos campos:Name(string) yData(de tipo T). Esto representa cualquier artículo con datos asociados de un tipo flexible.Crea una estructura genérica
Container[T any]con un solo campoItemsque contenga un slice deItem[T]. Este contenedor puede almacenar múltiples artículos del mismo tipo de dato.Crea una estructura genérica restringida
PricedItem[T int | float64]con tres campos:Name(string),Quantity(int), yPrice(de tipo T). La restricción asegura que los precios sean siempre numéricos.Implementa una función
NewContainer[T any]() *Container[T]que cree y devuelva un puntero a un Container vacío.Implementa un método
Adden*Container[T]que reciba un nombre (string) y datos (T), cree un Item y lo añada al slice Items del contenedor.Implementa un método
CountenContainer[T]que devuelva el número de artículos en el contenedor.main.go: Demuestra tus estructuras genéricas con diferentes tipos.Lee un tipo de artículo (
"string","int", o"priced"), luego lee un conteo seguido de los detalles del artículo.Para el tipo
"string": Lee pares de nombre y datos de tipo string. Crea unContainer[string], añade todos los artículos, luego imprime cada artículo como[Name]: [Data]seguido del conteo total.Para el tipo
"int": Lee pares de nombre y datos de tipo entero. Crea unContainer[int], añade todos los artículos, luego imprime cada artículo como[Name]: [Data]seguido del conteo total.Para el tipo
"priced": Lee tripletes de nombre, cantidad y precio (como float). Crea instancias dePricedItem[float64]directamente e imprime cada una como[Name] x[Quantity] @ [Price]con el precio mostrando un decimal.Imprime la línea del conteo como:
"Total items: [count]"
Se proporcionarán las siguientes entradas:
- Línea 1: Tipo de artículo (
"string","int", o"priced") - Línea 2: Conteo de artículos (entero)
- Líneas siguientes: Detalles del artículo basados en el tipo
Por ejemplo, dado:
string
3
Book
Fiction Novel
Pen
Blue Ink
Notebook
Lined PaperTu salida debería ser:
Book: Fiction Novel
Pen: Blue Ink
Notebook: Lined Paper
Total items: 3Y dado:
int
2
Apples
50
Oranges
30Tu salida debería ser:
Apples: 50
Oranges: 30
Total items: 2Y dado:
priced
2
Widget
10
19.99
Gadget
5
49.50Tu salida debería ser:
Widget x10 @ 19.9
Gadget x5 @ 49.5
Total items: 2Observa cómo la misma estructura Container funciona perfectamente tanto con strings como con enteros, mientras que PricedItem utiliza una restricción para asegurar que solo se puedan usar tipos numéricos para los precios. Debes especificar explícitamente el parámetro de tipo al crear instancias de estas estructuras genéricas.
Hoja de referencia
Una struct genérica te permite crear estructuras de datos que funcionan con cualquier tipo manteniendo la seguridad de tipos.
Para definir una struct genérica, coloca el parámetro de tipo después del nombre de la struct:
type Box[T any] struct {
Value T
}Al crear una instancia de una struct genérica, debes especificar explícitamente el tipo (Go no puede inferirlo):
intBox := Box[int]{Value: 42}
strBox := Box[string]{Value: "hello"}Las structs genéricas pueden tener múltiples parámetros de tipo:
type Pair[K, V any] struct {
Key K
Value V
}
p := Pair[string, int]{Key: "age", Value: 25}Puedes aplicar restricciones para asegurar que los tipos almacenados admitan operaciones específicas:
type NumberBox[T int | float64] struct {
Value T
}
nb := NumberBox[float64]{Value: 3.14}Las structs genéricas pueden contener campos que utilicen el parámetro de tipo, incluyendo slices:
type Container[T any] struct {
Items []T
}Puedes definir métodos en structs genéricas y crear funciones constructoras genéricas:
func NewContainer[T any]() *Container[T] {
return &Container[T]{Items: []T{}}
}
func (c *Container[T]) Add(item T) {
c.Items = append(c.Items, item)
}Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
func main() {
scanner := bufio.NewScanner(os.Stdin)
// Leer el tipo de elemento
scanner.Scan()
itemType := scanner.Text()
// Leer la cantidad
scanner.Scan()
count, _ := strconv.Atoi(scanner.Text())
switch itemType {
case "string":
// TODO: Crear un Container[string] usando NewContainer
// TODO: Leer 'count' elementos (cada uno tiene el nombre en una línea y los datos en la siguiente)
// TODO: Agregar cada elemento al contenedor
// TODO: Imprimir cada elemento como "[Name]: [Data]"
// TODO: Imprimir "Total items: [count]" usando el método Count
case "int":
// TODO: Crear un Container[int] usando NewContainer
// TODO: Leer 'count' elementos (cada uno tiene el nombre en una línea y los datos enteros en la siguiente)
// TODO: Agregar cada elemento al contenedor
// TODO: Imprimir cada elemento como "[Name]: [Data]"
// TODO: Imprimir "Total items: [count]" usando el método Count
case "priced":
// TODO: Crear un slice para contener instancias de PricedItem[float64]
// TODO: Leer 'count' elementos (cada uno tiene nombre, cantidad y precio en líneas separadas)
// TODO: Crear un PricedItem[float64] para cada uno y agregarlo al slice
// TODO: Imprimir cada elemento como "[Name] x[Quantity] @ [Price]" con el precio mostrando un decimal
// TODO: Imprimir "Total items: [count]"
}
_ = scanner // Usar scanner para leer la entrada
_ = count // Usar count para el bucle
}
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 Pool