Menu
Coddy logo textTech

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: 25

Tambié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.

challenge icon

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) y Data (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 campo Items que contenga un slice de Item[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), y Price (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 Add en *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 Count en Container[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 un Container[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 un Container[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 de PricedItem[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 Paper

Tu salida debería ser:

Book: Fiction Novel
Pen: Blue Ink
Notebook: Lined Paper
Total items: 3

Y dado:

int
2
Apples
50
Oranges
30

Tu salida debería ser:

Apples: 50
Oranges: 30
Total items: 2

Y dado:

priced
2
Widget
10
19.99
Gadget
5
49.50

Tu salida debería ser:

Widget x10 @ 19.9
Gadget x5 @ 49.5
Total items: 2

Observa 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
}
quiz iconPonte a prueba

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