En un artículo previo nos preguntamos si Go podía ser un lenguaje orientado a objetos, y llegamos a la conclusión de que sí, pero que debía ser utilizado de la manera correcta y no llevando el modo en que aplicamos la OO en los otros lenguajes.
En este artículo queremos explicar como funcionan los structs y como utilizarlos de la manera correcta, para muchos puede parece algo básico, pero no está demás repasar un poco.
Adiós clases
Como ya se ha comentado, en Go no hay clases, ¿entonces cómo modelamos nuestros objetos? Pues con structs.
Los structs no son más que una colección tipada de campos. Son útiles para agrupaciones de datos que forman registros.
Los structs al igual que cualquier tipo en Go permiten que se le añadan métodos modelando así el comportamiento del mismo, de manera similar a como funciona una clase.
¿Cómo construyo mis structs?
Los structs no dejan de ser un tipo más dentro de GO y podemos construirlos igual que cualquier otro tipo.
package main
import (
"fmt"
)
func main() {
var gopher = struct {
Name string
Age int
Color string
}{"Jack", 10, "Blue"}
fmt.Println(gopher)
}
Go Playground: https://play.golang.org/p/N7E_pHXQ48I
Esta práctica no es la más común y lo que se suele utilizar son los typed structs o type alias struct, que vienen a ser como cualquier type alias que hayamos visto anteriormente en Go salvo que en los structs suele pasar desapercibido y entenderse que es la manera natural de ser.
package main
import (
"fmt"
)
func main() {
g := gopher{"Jack", 10, "Blue"}
fmt.Println(g)
}
type gopher struct {
Name string
Age int
Color string
}
Go Playground: https://play.golang.org/p/aRcAwSHM-qz
Dando comportamiento a mi struct
Hasta este punto, tenemos un struct de Gopher pero por si sólo este Gopher no hace nada, más que indicarnos su nombre, edad y color. Vamos a hacer que nuestro Gopher pueda saludar.
package main
import (
"fmt"
)
func main() {
g := &gopher{"Jack", 10, "Blue"}
g.SayHi()
}
type gopher struct {
Name string
Age int
Color string
}
func (g *gopher) SayHi() {
fmt.Printf("%s say: Hi Friends of Go!", g.Name)
}
Go Playground: https://play.golang.org/p/fqVm4pK8XOS
Como podemos ver en el ejemplo anterior, le hemos proporcionado a nuestro Gopher la capacidad de decir hola, esto se logra indicándole a la función que pertenece al struct en cuestión func (g *gopher)
en nuestro caso lo hemos indicado como puntero, pero puede también se puede referenciar por valor func (g gopher)
.
De esta forma no sólo tendremos una colección de campos tipados sino que además sabremos que esa colección tiene un comportamiento dentro de nuestro programa.
Como hemos visto en el ejemplo anterior, hemos inicializado el struct Gopher sin una constructora y es que Go no tiene constructoras. Pero esta manera de inicializar un struct puede acarrear diferentes problemas, por ejemplo podríamos inicializar, con valores por defectos el struct haciendo que nuestro programa se comporte de manera incorrecta.
package main
import (
"fmt"
)
func main() {
var g gopher
g.SayHi()
}
type gopher struct {
Name string
Age int
Color string
}
func (g *gopher) SayHi() {
fmt.Printf("%s say: Hi Friends of Go!", g.Name)
}
Go Playground: https://play.golang.org/p/KVMeaDBLVDx
Inicializando mi struct
Para evitar errores como los mencionados anteriormente, existe una convención utilizada en Go para inicializar nuestros structs que no es otra que valernos de una función, NewT(parameters)
donde T
representa el tipo del struct, por ejemplo, func NewGopher(name, color string, age int){...}
, pero si el paquete sólo define un tipo, no será necesario especificarlo, func New(name, color string, age int){...}
, ya que queda implicito cuando se crea, g := gopher.New("Jack", "Blue", 10)
Esto además tiene otro sentido y es que podemos hacer que sus atributos no estén exportados, y tengan que ser siempre modificados a partir de los métodos que expongamos, veamos un ejemplo.
package gopher
type Gopher struct {
name string
age int
color string
}
func New(n, c string, a int) *Gopher {
return &Gopher{name:n, color:c, age:a}
}
func (g * Gopher) SayHi() {
fmt.Printf("%s say: Hi Friends of Go!", g.name)
}
Go Playground: https://play.golang.org/p/Xhe1psvcLHc
Conclusión
Hemos visto que Go no tiene clases pero que podemos utilizar los structs de manera algo similar, ahora ya somos capaces de darle un comportamiento a nuestros structs así como protegernos de que puedan modificarlos desde cualquier lugar.
Esto que explicamos aquí no es que tenga que ser siempre aplicado al 100%, recordemos que los structs no son clases, y que además tienen sus propios comportamientos dentro del lenguaje, que en según que casos nos vendrán genial, no siempre tendremos que enfocarlo todo a realizar OOP sino que habrá que evaluar que nos cuadra más en cada momento.
En el próximo artículo de la serie, explicaremos qué es eso de la composición y cómo utilizarla.
Recordamos que cualquier duda o comentario podéis dejarla por aquí o vía twitter(@FriendsofGoTech) estaremos encantados de comentar con vosotros.