No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Stringers: personalizar el output de Structs

24/36
Recursos

¿Cómo personalizar la salida de structs en Go?

Personalizar la salida en la consola es crucial para que nuestro código sea más legible y profesional. En los lenguajes de programación, tener una salida por consola bien estructurada es esencial para facilitar la comprensión y el mantenimiento del código. En el entorno del lenguaje Go, personalizar la salida de estructuras (structs) es una tarea sencilla y eficaz mediante el uso de una función que implemente el método de cadena (string).

¿Qué es un struct en Go?

Un struct en Go es una colección de campos de datos. Es similar a una clase en otros lenguajes de programación, pero sin funcionalidades como la herencia. El uso de structs permite organizar datos relacionados y trabajar con ellos de manera eficiente.

Por ejemplo, consideremos un struct PC que modela un ordenador con información sobre la RAM, el disco y el fabricante. Este struct podría definirse de la siguiente manera:

type PC struct {
    RAM   int
    Brand string
    Disk  int
}

¿Cómo crear una función string para un struct?

El lenguaje Go permite definir métodos en estructuras. Para personalizar el output de un struct, se puede implementar el método String(). Este método debe adherirse a unas convenciones específicas para asegurar el formato correcto.

A continuación, se muestra cómo implementar una función String() para nuestro struct PC:

func (miPC PC) String() string {
    return fmt.Sprintf("Tengo %d gigabytes de RAM, %d gigabytes de disco y es una %s.", miPC.RAM, miPC.Disk, miPC.Brand)
}

En este código:

  • Implementamos el método String() que retorna un string personalizado.
  • Utilizamos la función fmt.Sprintf() para construir el string de salida.
  • Especificamos patrones como %d y %s dentro de la cadena para formatear enteros y strings, respectivamente.

¿Cuál es el procedimiento para imprimir el struct en la consola?

Una vez implementado el método String(), el struct puede imprimirse directamente con un fmt.Printf() o fmt.Println(), y automáticamente se utilizará el formato personalizado que definimos. Aquí hay un ejemplo:

func main() {
    miPC := PC{RAM: 16, Brand: "MSI", Disk: 100}
    fmt.Println(miPC)
}

La línea anterior producirá una salida como:

Tengo 16 gigabytes de RAM, 100 gigabytes de disco y es una MSI.

¿Por qué es eficiente esta técnica?

Esta técnica es eficiente porque encapsula la lógica de formateo dentro del struct, eliminando la necesidad de definir cómo se debe imprimir cada instancia. Permite mejorar la mantenibilidad y legibilidad del código. Cuando se trabaja a gran escala o se desarrollan interfaces de usuario complejas, la programación de este tipo de métodos es una práctica recomendable en términos de eficiencia y claridad.

Recomendaciones adicionales

  • Consistencia: Asegúrate de que todos los structs relevantes tengan implementaciones del método String() si deseas obtener una salida consistente.
  • Legibilidad: Mantén el formato de salida claro y alineado con las necesidades de los usuarios finales o los desarrolladores que interactuarán con el código.
  • Documentación: Añade comentarios siempre que sea necesario para explicar la lógica detrás del método de formateo, especialmente cuando el formato es complejo.

Esta personalización te ayudará a crear aplicaciones más amigables y robustas, permitiéndote abordar desafíos de manera eficaz. ¡Continúa explorando las funcionalidades de Go y aplica este conocimiento en tus futuros proyectos!

Aportes 20

Preguntas 1

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

  • La estructura de datos " Struct " tiene un método llamado " String " , que podemos sobrescribir para personalizar la salida a consola de los datos del struct.

Por lo que puedo ver, lo que va entre func y el nombre de la funcion (miPC pc) es lo que asocia a la funcion con la struct, pero no necesariamente con el “objeto” miPC, ahi puede ir cualquier nombre. Ej:

// Stringer
func (juanito pc) String() string {
	return fmt.Sprintf("PC: %s con %dGB RAM y SSD %dGB", juanito.brand, juanito.ram, juanito.disk)
}

y por otro lado, note algo que esta bueno me parece. Como el struct se inicializa con pares clave:valor entre llaves {}, no hace falta que esten en orden, se lo cambie y funciona igual:

type pc struct {
	ram   int
	disk  int
	brand string
}
...
	// con la struct
	miPC := pc{ram: 16, brand: "Dell", disk: 480}
	fmt.Println(miPC) // llama a String

Creo que los stringers es muy parecido a lo que Python tiene para las clases que se llama el método __src__

Hay otra manera:

fmt.Printf("%+v", myPC)

Escribe los campos y los valores, es bastante legible sin sobrescribir el método String

Este es mi codigo de esta clase 👇

package main

import "fmt"

type pc struct {
	ram   int
	os string
	disk  int
}

func (myPC pc) String() string {
	return fmt.Sprintf("Tengo %d GB de RAM, %d GB de Disco y es un sistema %s.", myPC.ram, myPC.disk, myPC.os)
}

func main() {
	myPC := pc{ram: 16, os: "Linux", disk: 100}

	fmt.Println(myPC)
}

Esto esta mas documentado aquí.

https://github.com/fatih/color 🎨 para darle colores a los mensajes en consola

func (myPC PC) String() string {
  str := color.HiYellowString("My [Pc] is/have:")
  str += color.HiMagentaString("\n * brand %s", myPC.brand)
  str += color.HiCyanString("\n * %d GB of RAM", myPC.ram)
  str += color.HiGreenString("\n * %d GB od disk", myPC.disk)
  return str
}

salida:

Brutal.

package main

import "fmt"

type pc struct {
	tarjetagrafica string
	ram            int
	disk           int
}
//Esta funcion trabajara sobre la variable y la struct definidas en su principio.
//Luego se escribira la funcion "String" y despues el tipo de dato que retornara.
func (miPc pc) String() string {
	return fmt.Sprintf("Tengo %dGB de RAM, %dGB de disco y una targeta grafica %s", miPc.ram, miPc.disk, miPc.tarjetagrafica)

}

//con el metodo stringers podremos modificar los output's de las structs en consola.

func main() {
	miPc := pc{ram: 16, tarjetagrafica: "3080ti", disk: 1000}
	fmt.Println(miPc)
//De esta manera personalizaremos nuestro output en consola.
}

Los stringers son parecidos al metódo toString() de Java ☕

Uff!! El famoso toString() de Java ❣️

```js package car import "encoding/json" // Modelo Publico de Car type CarPublic struct { Brand string Model string Year int Seats int } func (c *CarPublic) ToMap() (res map[string]interface{}) { a, err := json.Marshal(c) if err != nil { panic(err) } json.Unmarshal(a, &res) return } ``` Lo que hice fue buscar la manera de convertir el struct en un map. Este método es agnóstico a la estructura del struct, por lo que se puede aplicar a cualquiera sin tener que hardcodear los atributos. Posteriormente, definí en un paquete aparte llamado utils, un método llamado PrintStruct que imprime cada par key:value por línea. ```js func PrintStruct(f map[string]interface{}) { for i, value := range f { fmt.Printf("%s: %v\n", i, value) } } ```

todo esto me recueda a C jaja y viejo y traumatico C o C++ jaja

Si en el editor les sale que es mala practica mezclar valor y puntero y quieres hacerlo con puntero, es de esta forma

package main

import "fmt"

type pc struct {
	ram   int
	disk  int
	brand string
}

func (myPC *pc) String() string {
	return fmt.Sprintf("RAM: %d, Disk: %d, Brand: %s", myPC.ram, myPC.disk, myPC.brand)
}

func main() {
	myPC := pc{ram: 16, disk: 512, brand: "Lenovo"}
	fmt.Println(&myPC)
}

Code Resume

package main

import (
	"curso_golang_platzi/src/mypackage"
	"fmt"
)

func main() {
	var myPc mypackage.Pc
	myPc.SetBrand("MSI")
	myPc.SetRam(16)
	myPc.SetDisk(1000)
	fmt.Println(myPc)
}
package mypackage

import "fmt"

type Pc struct {
	brand string
	ram   uint
	disk  uint
}

func (p Pc) GetBrand() string {
	return p.brand
}

func (p *Pc) SetBrand(brand string) {
	p.brand = brand
}

func (p Pc) GetRam() uint {
	return p.ram
}

func (p *Pc) SetRam(ram uint) {
	p.ram = ram
}

func (p Pc) GetDisk() uint {
	return p.disk
}

func (p *Pc) SetDisk(disk uint) {
	p.disk = disk
}

//Stringers
//This way we overload the print function for our struct
func (p Pc) String() string {
	return fmt.Sprintf("RAM: %d GB, DISK: %d GB, BRAND: %s", p.ram, p.disk, p.brand)
}

func mypackage() {

}

Link de mi repositorio Curso Golang Basico:
https://github.com/JohnHM1/Go
:3
Brutal.

Ahora viendo esta clase mejoraré la funcion que cree en el Reto anterior:

Funcion anterior:

func (myDoctor DoctorPublic) ShowData() string {
	age := strconv.Itoa(myDoctor.Age)
	return "Name:" + myDoctor.Name + " Speciality:" + myDoctor.Speciality + " Age:" + age

}

//Y para mostrarse
fmt.Println(myDoctor.ShowData())

Funcion mejorada:

func (myDoctor DoctorPublic) String() string {
	return fmt.Sprintf("Doctor information: \nNombre: %s \nSpeciality: %s \nAge: %d", myDoctor.Name, myDoctor.Speciality,
		myDoctor.Age)

}
//Para mostrarse
fmt.Println(myDoctor)

La lección en código:

package main

import "fmt"

type computer struct {
	ram   int
	brand string
	disk  int
}

func (myComputer computer) String() string {

	fmt.Println()
	fmt.Println("=========================================================================")
	fmt.Println("... entrando a función sobrescrita de representación string del struct...")
	fmt.Println("-------------------------------------------------------------------------")

	result := fmt.Sprintf("CantidadRam: %d GB, cantidadHD: %d GB, marca: %s",
		myComputer.ram,
		myComputer.disk,
		myComputer.brand)

	fmt.Println("-------------------------------------------------------------------------")
	fmt.Println("... Saliendo a función sobrescrita de representación string del struct...")
	fmt.Println("_________________________________________________________________________")
	fmt.Println()

	return result
}

func main() {

	fmt.Println()
	fmt.Println("============================================================================================")
	fmt.Println("Sobrescribiendo funciones propias de objetos")
	fmt.Println()
	fmt.Println("... ejemplo usando Stringers para cadenas de texto.")
	fmt.Println("============================================================================================")
	fmt.Println()

	myComputer := computer{ram: 16, brand: "msi", disk: 100}

	fmt.Println("Imprimiendo con 'fmt' el struct con su funcion 'string' sobrescrita:")
	fmt.Println("Este es el struct >>>", myComputer)
	fmt.Println()

}
 

Personalización

// Output Anterior:
XYR
16
32
64
128
// Output Actual:
1.RAM: 16GB
2.DISK: 500GB
3.BRAND: XYR

Código usado:

//[...]
func main(){
  myPc := pkg.Pc{Ram: 16, Disk: 500, Brand: "XYR"}
  fmt.Println(myPc.String())
}

Package:

// [...]
type Pc struct {
	Ram, Disk int
	Brand     string
}
func(myPc Pc) String() string{
  return fmt.Sprintf("1.RAM: %dGB\n2.DISK: %dGB \n3.BRAND:  %s", myPc.Ram, myPc.Disk, myPc.Brand)
} 

Interesante

Los stringers nos sirven para modificar la forma en la que se imprime un struct, se hacen de la siguiente manera:

package main

type pc struct {
	ram int
	model string
}

func (myPc pc) String() string {
	return fmt.Sprintf("Es un %s model, y tiene %d RAM")
}

func main() {
	myPc := pc{model: "Asus", ram: 16}
	fmt.Println(myPc)
}

El output de el Println sera de esta forma:

$ go run main.go
Es un Asus model, y tiene 16 RAM

Si no hubieramos creado el stringer el output seria:

$ go run main.go
{16 500 Msi}
// Cuando imprimamos el objeto
func (myPc pc) String() string {
	return fmt.Sprintf("Tengo %d GB RAM, %d GB Disco y es una %s", myPc.ram, myPc.disk, myPc.brand)
}