Go

Go

Tutorial Go: Estructuras structs

Go y structs: Descubre cómo definir, instanciar y utilizar structs para la organización de datos en programación orientada a objetos en Golang.

Aprende Go GRATIS y certifícate

Definición y uso de structs

Los structs son un tipo de dato compuesto que permite agrupar variables bajo un mismo nombre, lo cual es fundamental para la organización y manejo de datos complejos. Un struct se define utilizando la palabra clave type, seguida del nombre del struct y la palabra clave struct. Dentro de las llaves {}, se especifican los campos, que son las variables que componen el struct. Cada campo tiene un nombre y un tipo asociado.

Ejemplo básico de definición de un struct en Go:

type Persona struct {
    Nombre string
    Edad   int
}

En este ejemplo, Persona es un struct con dos campos: Nombre, de tipo string, y Edad, de tipo int. Los structs permiten encapsular datos relacionados, lo que facilita su manipulación y acceso.

Para instanciar un struct en Go, se utiliza la sintaxis de llaves {} después del nombre del struct. Se pueden inicializar los campos en el orden en que fueron definidos, o bien utilizar una notación de pares clave-valor para mayor claridad. Aquí se muestra un ejemplo:

p := Persona{"Juan", 30}
// o utilizando pares clave-valor
p := Persona{Nombre: "Juan", Edad: 30}

Una vez instanciado, se puede acceder y modificar los campos de un struct mediante el operador punto (.). Esta capacidad de acceso es crucial para manipular los datos encapsulados dentro del struct. Por ejemplo:

fmt.Println(p.Nombre) // Imprime: Juan
p.Edad = 31
fmt.Println(p.Edad)   // Imprime: 31

Es importante destacar que los structs en Go son tipos de valor, lo que significa que cuando se asigna un struct a una nueva variable, se crea una copia del mismo. Cualquier modificación en la copia no afectará al original. Esto se diferencia de los tipos de referencia que se encuentran en otros lenguajes.

Para modificar directamente los datos de un struct sin crear copias, se deben utilizar punteros. Los punteros permiten pasar la dirección de memoria del struct, permitiendo cambios directos en el objeto original.

Ejemplo que ilustra cómo trabajar con punteros a structs:

func incrementarEdad(p *Persona) {
    p.Edad++
}

p := Persona{Nombre: "Juan", Edad: 30}
incrementarEdad(&p)
fmt.Println(p.Edad) // Imprime: 31

En este caso, la función incrementarEdad recibe un puntero a Persona, y mediante el operador * y &, se puede modificar el campo directamente en la memoria original del objeto.

Los structs en Go también pueden tener métodos asociados, lo que permite definir comportamientos específicos para los datos que contienen. Los métodos se definen fuera del struct, pero se asocian a él especificando un receptor de método.

Ejemplo de cómo definir un método para el struct Persona:

func (p Persona) Saludar() string {
    return "Hola, me llamo " + p.Nombre
}

p := Persona{Nombre: "Juan", Edad: 30}
fmt.Println(p.Saludar()) // Imprime: Hola, me llamo Juan

En este ejemplo, el método Saludar pertenece al struct Persona y devuelve un saludo utilizando el campo Nombre. La capacidad de definir métodos proporciona una manera organizada de implementar funcionalidades específicas para los datos encapsulados en los structs.

Campos anónimos y composición

Los campos anónimos en structs permiten la inclusión de tipos sin especificar un nombre explícito para el campo. Esto facilita la composición y la reutilización de código al permitir que un struct herede los métodos y propiedades de otro tipo, sin necesidad de definir explícitamente un nombre para cada campo. Esta característica se asemeja a la herencia en otros lenguajes orientados a objetos, aunque Go no la implementa de manera directa.

Un campo anónimo se define simplemente especificando el tipo, sin un identificador. Este tipo actúa como el nombre del campo y permite acceder directamente a sus métodos y propiedades.

Ejemplo de cómo se pueden definir campos anónimos en un struct:

type Direccion struct {
    Ciudad, Pais string
}

type Persona struct {
    Nombre string
    Edad   int
    Direccion
}

En este ejemplo, Direccion es un struct que se incluye como un campo anónimo dentro del struct Persona. Así, Persona hereda los campos Ciudad y Pais de Direccion.

La composición en Go se utiliza para construir estructuras complejas a partir de otras más simples, fomentando la reutilización de código y la modularidad. Los campos anónimos son una herramienta esencial para lograr esta composición. Cuando un struct incluye otro struct como campo anónimo, se pueden acceder a sus campos y métodos directamente desde el struct que lo contiene:

p := Persona{
    Nombre: "Juan",
    Edad:   30,
    Direccion: Direccion{
        Ciudad: "Madrid",
        Pais:   "España",
    },
}

fmt.Println(p.Ciudad) // Imprime: Madrid

En este caso, p.Ciudad accede directamente al campo Ciudad del struct Direccion, gracias a que Direccion es un campo anónimo en Persona.

Es importante destacar que, si un struct anónimo y el struct que lo contiene tienen métodos con el mismo nombre, se debe especificar explícitamente a cuál método se refiere para evitar ambigüedades. Esta especificidad se logra utilizando el nombre del tipo anónimo como prefijo:

func (d Direccion) Mostrar() string {
    return d.Ciudad + ", " + d.Pais
}

func (p Persona) Mostrar() string {
    return "Nombre: " + p.Nombre + ", " + p.Direccion.Mostrar()
}

fmt.Println(p.Mostrar()) // Imprime: Nombre: Juan, Madrid, España

En este ejemplo, p.Mostrar() llama al método Mostrar del struct Persona, que a su vez llama al método Mostrar del struct Direccion utilizando p.Direccion.Mostrar(). Este enfoque permite definir comportamientos específicos y diferenciados para cada parte de la composición.

Comparación con clases en otros lenguajes

En el contexto de la programación orientada a objetos, las clases son un concepto central en muchos lenguajes como Java, C++ y Python. Sin embargo, Go, a pesar de soportar principios de programación orientada a objetos, no utiliza clases. En su lugar, emplea structs para encapsular datos y métodos. Esta distinción es crucial, ya que los structs en Go carecen de algunas características tradicionales de las clases, como la herencia directa, pero ofrecen otras ventajas como la composición.

En lenguajes como Java, una clase define tanto el comportamiento como el estado de los objetos, permitiendo la herencia para reutilizar código y extender funcionalidades. En Go, este comportamiento se logra a través de la composición y la implementación de interfaces, lo que fomenta un diseño más modular y flexible. Los métodos se asocian a structs mediante receptores, permitiendo que los structs actúen de manera similar a las clases, pero sin necesidad de heredar de una superclase.

Un aspecto clave en la comparación es la herencia. Mientras que en lenguajes como C++ y Java la herencia es un mecanismo fundamental para compartir comportamiento entre clases, Go utiliza la composición. La composición en Go se logra mediante la inclusión de otros structs como campos anónimos, proporcionando una forma de reutilizar código sin la complejidad de la herencia múltiple. Este enfoque favorece la claridad y la mantenibilidad del código.

Por ejemplo, en Java se podría definir una clase Persona y una subclase Empleado que hereda de Persona:

class Persona {
    String nombre;
    int edad;
}

class Empleado extends Persona {
    String empresa;
}

En Go, este mismo concepto se implementa utilizando composición:

type Persona struct {
    Nombre string
    Edad   int
}

type Empleado struct {
    Persona
    Empresa string
}

En este ejemplo, Empleado incluye Persona como un campo anónimo, permitiendo el acceso directo a sus campos y métodos sin necesidad de herencia.

Otro punto de comparación es la visibilidad de los campos y métodos. En lenguajes como Java, se utilizan modificadores de acceso como public, private, y protected para controlar el acceso a los miembros de una clase. Go, en cambio, utiliza la convención de capitalización: los identificadores que comienzan con una letra mayúscula son exportados y accesibles desde otros paquetes, mientras que aquellos que comienzan con una letra minúscula son privados al paquete.

El polimorfismo en Go se logra a través de interfaces, en lugar de herencia de clases. Las interfaces en Go son conjuntos de métodos que un tipo debe implementar. Cualquier tipo que implemente los métodos de una interfaz es considerado una implementación de esa interfaz, lo que proporciona un polimorfismo más flexible y menos acoplado que la herencia tradicional.

type Saludador interface {
    Saludar() string
}

func (p Persona) Saludar() string {
    return "Hola, soy " + p.Nombre
}

var s Saludador = Persona{Nombre: "Juan"}
fmt.Println(s.Saludar()) // Imprime: Hola, soy Juan

En este ejemplo, Persona implementa la interfaz Saludador al definir el método Saludar, permitiendo su uso polimórfico.

Aunque Go no implementa clases en el sentido tradicional, su enfoque hacia la programación orientada a objetos mediante structs, composición e interfaces ofrece una alternativa robusta y eficiente. Este modelo promueve la claridad y la simplicidad, evitando la complejidad inherente a las jerarquías de clases y la herencia múltiple.

Aprende Go GRATIS online

Todas las lecciones de Go

Accede a todas las lecciones de Go y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a Go y certifícate

Certificados de superación de Go

Supera todos los ejercicios de programación del curso de Go y obtén certificados de superación para mejorar tu currículum y tu empleabilidad.

En esta lección

Objetivos de aprendizaje de esta lección

  • Comprender la definición y estructura de los structs en Go.
  • Instanciar y manipular structs utilizando valores y punteros.
  • Implementar métodos asociados a structs.
  • Utilizar campos anónimos para lograr composición en structs.
  • Comparar el uso de structs con clases en otros lenguajes orientados a objetos.
  • Implementar la composición y las interfaces como alternativa a la herencia.