Simulando la POO en Golang

En el paradigma de la Programación Orientada a Objetos los cuatro pilares fundamentales son: Abstracción, Herencia, Encapsulamiento y Polimorfismo.
Go es un lenguaje cuyo objetivo de concepción fue la practicidad y no el purismo en cuanto a paradigma, pero nos cabe la pregunta, cómo podemos simular la orientación a objetos que obtenemos de manera natural en lenguajes como C# o Java.
Interfaces
La interfaz es un tipo que solo define un conjunto de firmas de métodos y son la base para poder pensar, si quiera, en cualquier atisbo de orientación a objetos en Golang. La implementación de una interfaz es implícita, bastará con implementar los métodos que define la interfaz en cuestión, no es necesario declararlo explícitamente.
type Animal interface {
//Metdodo abstracto Trasladate
trasladate()
}
Abstacción. Structs
A falta de clases en Golang están presentes las estructuras las cuales nos permiten definir tipos. La diferencia sustancial con las clases es que, las estructuras definen el estado pero no el comportamiento. El siguiente código muestra la estructura para definir un Mamífero.
// Clase abstracta
type Ave struct {
locomoción string
nombre string
especie string
}
// Implementacion del método abstracto de Animal
func (ave Ave) trasladate() {
fmt.Println("Me traslado " + ave.locomoción)
}
Al implementar el método abstracto de Animal está implicando que Ave implementa la interfaz Animal.
La composición como una forma de herencia.
Golang apuesta por la composición en lugar de la herencia. Para lograr la composición se utilizan structs incrustados.
De esta manera simularíamos una herencia, y podríamos afirmar que un Pingüino y un Gorrion poseen todos los campos de un Ave y sus propios campos.
type Pinguino struct {
Ave
carnivoro bool
}
type Gorrion struct {
Ave
tiene_arbol bool
}
Para crear una instancia de un struct que contenga otro struct incrustado debemos instanciar el struct incrustado en el momento de la instanciación del struct más externo.
gorrion := Gorrion{
Ave{"volando", "Tari", "Passer domesticus"}, true}
En este ejemplo podemos ver que, debido a que tanto el tipo Gorrion como el tipo Pingüino poseen (no son) un Ave y Ave implementa el método trasladate de la Interfaz Animal, Piguino y Gorrion también tendrán su método trasladate. Esto es lo más cercano a la herencia que podemos lograr en Go.
func main() {
gorrion := Gorrion{
Ave{"volando", "Tari", "Passer domesticus"}, true}
pingu := Pinguino{Ave{"nadando", "Pingu", "Aptenodytes patagonicus"}, true}
fmt.Println("Tipo Gorrion: ", gorrion)
gorrion.trasladate()
fmt.Println("Tipo Piguino: ", pingu)
pingu.trasladate()
}
La salida de este código será:

Es importante conocer, que aunque en una composición estamos simulando la herencia y “se heredan” todos los atributos y métodos de la Struct que se esté incrustando, el tipo resultante de la composición, a diferencia de lo que ocurre con la herencia, no puede ser tratado como un tipo de su struct “base” (tomo prestados los términos de la POO que realmente no existen en Go).
Continuando con el ejercicio de ejemplo, probemos a crear un slices de Ave e inicializarlo con las instancias de Gorrion y de Pingüino que hemos previamente creado.
func main() {
gorrion := Gorrion{
Ave{"volando", "Tari", "Passer domesticus"}, true}
pingu := Pinguino{Ave{"nadando", "Pingu", "Aptenodytes patagonicus"}, true}
fmt.Println("Tipo Gorrion: ", gorrion)
gorrion.trasladate()
fmt.Println("Tipo Piguino: ", pingu)
pingu.trasladate()
misAves := []Ave{pingu, gorrion}
fmt.Println(misAves)
}
Obtendremos este error:

Nos dice claramente que los tipos Gorrion y Pinguino no pueden ser tratados como un Ave, con esto vemos que Gorrion y Pingüino No Son Ave y que la composición no es igual a la herencia.
Sin embargo, este código no nos daría ningún error, pues estoy inicializándolo con el tipo Ave que tiene dentro cada uno:
misAves := []Ave{pingu.Ave, gorrion.Ave}
Encapsulamiento en Go.
El encapsulamiento en Go está definido a nivel de Package y no posee modificadores de visibilidad, si un atributo es declarado con letra inicial mayúsculas, significa que es exportado fuera del paquete, en cambio con letra inicial minúscula sería el equivalente al private en C# o Java.
Polimorfismo ¿Sí o No?
Si somos ligth en la definición que tenemos de Polimorfirmo, podemos afirmar que es posible lograrlo en Golang a través de las inferfaces. Ilustrémoslo con un ejemplo.
Definamos la sgte interfaz (por convención en Golang los nombres de las interfaces deben terminar en ´er´, para claridad de la ilustración lo omitiremos)
type AnimalDomestico interface {
juega() string
}
Interfaz AnimalDomestico con un método abstracto juega que devuelve un string.
Definamos también dos Structs.
type Perro struct {
cazador bool
}
// Implementacion del método abstracto
func (perro Perro) juega() string {
return "Busco el palito"
}
type Gato struct {
cant_vidas int
}
// Implementacion del método abstracto
func (gato Gato) juega() string {
return "Me siento en el regazo"
}
Struct Gato que también implementa la interfaz AnimalDomestico.
En nuestro main, instanciemos las Structs y creemos un slices de AnimalDomestico e inicialicémoslo con nuestras instancias. En este caso el código funcionará correctamente y al invocar el método juega de cada elemento del slices se comportará diferente, de acuerdo a la definición del método de cada instancia (Polimorfismo). En este caso perro y gato son tratados como AnimalDoméstico.
func main() {
perro := Perro{true}
gato := Gato{7}
fmt.Println("Tipo Perro: ", perro)
fmt.Println("Tipo Gato: ", gato)
misAnimales := []AnimalDomestico{perro, gato}
for _, animal := range misAnimales {
fmt.Println(animal.juega())
}
}
La salida de este código es:

Conclusiones.
Aunque no podemos afirmar que el lenguaje cumpla con el paradigma de la Orientación a Objetos sí podemos, con mayor o menor esfuerzo, lograr un diseño orientado a objetos.
Acá les dejo el link del código ejemplo: