Embedding en Go: integrando estructuras en estructuras

Esta semana continuando con el reto de #100DaysOfCode me encontré con otra capacidad que tiene Go para facilitar el uso de las estructuras de datos mediante un método llamado struct embedding.

Campos de datos

En Go, al igual que en muchos otros lenguajes de programación, es posible definir estructuras que contienen campos que refieren a otras estructuras. Esto se conoce como composición, un principio que proviene de la programación orientada a objetos.

Por ejemplo si estamos construyendo una herramienta para trabajar con figuras geométricas uno de los conceptos fundamentales es el de un punto en el plano cartesiano. En Go se realiza de la siguiente manera:

type Point struct {
	X, Y float64	
}

Si deseamos a partir de esto crear un punto con color podemos realizarlo de la siguiente manera:

type ColoredPoint struct {
    Point,
    Color string,
}

Hay un detalle muy importante a observar: no tuvimos que definir un campo nominal al añadir la estructura Point, sino que integramos la estructuraPoint en ColoredPoint.

Al integrar una estructura en Go se obtiene la capacidad de soportar una promoción de las funciones en la nueva estructura. Por ejemplo

 func main() {
    var bluePoint ColoredPoint
	bluePoint.Color = "blue"
	bluePoint.X = 10
	bluePoint.Y = 1.23
}

// imprime
Punto azul ColoredPoint blue Point {10.00, 1.23}

Al definir la variable bluePoint del tipo ColoredPoint podemos acceder a los campos X y Y directamente como si se tratasen de propiedades que fueron originalmente definidas en dicha estructura si así lo deseamos.

En este caso podemos también inicializar una variable del tipo ColoredPoint sin nombrar los campos ya que el compilador los infiere por nosotros:

func main() {
    redPoint := geometry.ColoredPoint{ geometry.Point{1, 1}, "red" }
    fmt.Println("Punto rojo", redPoint)
}

// imprime
Punto rojo ColoredPoint red Point {1.00, 1.00}

Métodos

La integración permite también utilizar los métodos asociados con la estructura integrada.

Por ejemplo para Point se ha definido una función que calcula la distancia con otro Point de la siguiente manera:

func (p Point) Distance(q Point) float64 {
	return math.Sqrt(math.Pow((q.X - p.X), 2) + math.Pow((q.Y - p.Y), 2))
}

Este método puede ser usado por ColoredPoint de forma directa

func main() {
    redPoint := ColoredPoint{ Point{1, 1}, "red" }
    bluePoint := ColoredPoint{ Point{10, 1.23}, "blue" }
    dRedBlue := redPoint.Distance(bluePoint)
    fmt.Println("Distancia de rojo a azul", dRedBlue)
}

// imprime
Distancia de rojo a azul 9.002

Integrar estructuras en Go permite que la composición sea más simple y legible facilitando su uso como clientes.

Pueden encontrar el código en este repositorio.