Fundamentos de Reflection
Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 83 de 107.
La reflexión permite que un programa examine y manipule su propia estructura en tiempo de ejecución. El paquete reflect de Go proporciona esta capacidad, permitiéndote inspeccionar tipos, leer campos de estructuras y llamar a métodos de forma dinámica.
Los dos tipos principales en la reflexión son reflect.Type y reflect.Value. Se obtienen utilizando reflect.TypeOf() y reflect.ValueOf():
import "reflect"
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
t := reflect.TypeOf(p)
v := reflect.ValueOf(p)
fmt.Println(t.Name()) // Person
fmt.Println(t.Kind()) // struct
fmt.Println(v.NumField()) // 2Puedes iterar sobre los campos de una estructura para examinar sus nombres, tipos y valores:
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
fmt.Printf("%s: %v\n", field.Name, value)
}
// Salida:
// Name: Alice
// Age: 30La reflexión es potente pero conlleva compensaciones: evita la comprobación de tipos en tiempo de compilación, se ejecuta más lentamente que el acceso directo y hace que el código sea más difícil de entender. Úsela con moderación para tareas como el marshaling de JSON, el mapeo ORM o la creación de utilidades genéricas donde el tipo no se conoce en tiempo de compilación.
Desafío
Fácil¡Vamos a construir un inspector de estructuras que utilice la reflexión para examinar cualquier struct e informar sobre su estructura! Este es un caso de uso práctico para la reflexión: crear utilidades que funcionen con tipos desconocidos en tiempo de compilación.
Organizarás tu código en dos archivos:
inspector.go: Crea tu utilidad de inspección basada en reflexión.Crea una función
InspectStruct(v any) stringque utilice el paquetereflectpara examinar cualquier struct que se le pase. Tu función debe devolver un informe formateado que contenga:- El nombre del tipo del struct
- El "kind" del struct (debe ser "struct")
- El número de campos
- Para cada campo: el nombre del campo, su tipo y su valor actual
El formato de salida debe ser:
Type: [TypeName] Kind: struct Fields: [count] - [FieldName] ([FieldType]): [Value] - [FieldName] ([FieldType]): [Value] ...Usa
reflect.TypeOf()para obtener información de tipo yreflect.ValueOf()para acceder a los valores de los campos. Itera a través de los campos usandoNumField(),Field(i)en el tipo para los metadatos, yField(i)en el valor para los valores reales.main.go: Define structs de ejemplo y usa tu inspector.Lee un tipo de struct (
productoemployee) y sus valores de campo, luego crea el struct apropiado e inspéccionalo.Para
product: Crea un structProductcon los camposName(string),Price(float64) eInStock(bool). Lee el nombre, el precio y el estado del stock.Para
employee: Crea un structEmployeecon los camposID(int),Name(string) yDepartment(string). Lee el ID, el nombre y el departamento.Pasa el struct creado a
InspectStructe imprime el resultado.
Se proporcionarán las siguientes entradas:
- Línea 1: Tipo de struct (
productoemployee) - Líneas siguientes: Valores de los campos para ese tipo de struct
Por ejemplo, dado:
product
Laptop
999.99
trueTu salida debería ser:
Type: Product
Kind: struct
Fields: 3
- Name (string): Laptop
- Price (float64): 999.99
- InStock (bool): trueY dado:
employee
42
Alice Johnson
EngineeringTu salida debería ser:
Type: Employee
Kind: struct
Fields: 3
- ID (int): 42
- Name (string): Alice Johnson
- Department (string): EngineeringY dado:
product
Headphones
79.50
falseTu salida debería ser:
Type: Product
Kind: struct
Fields: 3
- Name (string): Headphones
- Price (float64): 79.5
- InStock (bool): falseObserva cómo tu inspector funciona con diferentes tipos de struct sin conocer su estructura de antemano; ¡este es el poder de la reflexión! La misma función InspectStruct maneja dinámicamente tanto los structs Product como Employee.
Hoja de referencia
El paquete reflect permite que los programas examinen y manipulen su propia estructura en tiempo de ejecución.
Los dos tipos principales son reflect.Type y reflect.Value, que se obtienen utilizando reflect.TypeOf() y reflect.ValueOf():
import "reflect"
type Person struct {
Name string
Age int
}
p := Person{Name: "Alice", Age: 30}
t := reflect.TypeOf(p)
v := reflect.ValueOf(p)
fmt.Println(t.Name()) // Person
fmt.Println(t.Kind()) // struct
fmt.Println(v.NumField()) // 2Itere sobre los campos de la estructura para examinar sus nombres, tipos y valores:
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
value := v.Field(i)
fmt.Printf("%s: %v\n", field.Name, value)
}
// Output:
// Name: Alice
// Age: 30La reflexión omite la comprobación de tipos en tiempo de compilación, se ejecuta más lentamente que el acceso directo y hace que el código sea más difícil de entender. Utilícela para tareas como la serialización JSON (marshaling), el mapeo ORM o la creación de utilidades genéricas donde el tipo no se conoce en tiempo de compilación.
Pruébalo tú mismo
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
// Struct Product con los campos Name, Price e InStock
type Product struct {
Name string
Price float64
InStock bool
}
// Struct Employee con los campos ID, Name y Department
type Employee struct {
ID int
Name string
Department string
}
func main() {
reader := bufio.NewReader(os.Stdin)
// Lee el tipo de struct
structType, _ := reader.ReadString('\n')
structType = strings.TrimSpace(structType)
if structType == "product" {
// Lee los campos de product
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
priceStr, _ := reader.ReadString('\n')
priceStr = strings.TrimSpace(priceStr)
price, _ := strconv.ParseFloat(priceStr, 64)
inStockStr, _ := reader.ReadString('\n')
inStockStr = strings.TrimSpace(inStockStr)
inStock, _ := strconv.ParseBool(inStockStr)
// TODO: Crea un struct Product con los valores leídos
// TODO: Llama a InspectStruct con el product e imprime el resultado
} else if structType == "employee" {
// Lee los campos de employee
idStr, _ := reader.ReadString('\n')
idStr = strings.TrimSpace(idStr)
id, _ := strconv.Atoi(idStr)
name, _ := reader.ReadString('\n')
name = strings.TrimSpace(name)
department, _ := reader.ReadString('\n')
department = strings.TrimSpace(department)
// TODO: Crea un struct Employee con los valores leídos
// TODO: Llama a InspectStruct con el employee e imprime el resultado
}
}
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 Pool12Conceptos avanzados de POO
Patrón Functional OptionsPatrón Builder en GoEncadenamiento de métodosAlias de tipos vs. definicionesFundamentos de ReflectionDescripción general de generación de código