Nos quedamos en la última entrega con una estructura que Animal hacía de tipo "base" del tipo Cat. El problema venía cuando queríamos crear un patrón factory method, ya que no podemos apuntar con un puntero a Animal a una estructura Cat.
func New() *Animal {
return &Cat{} // Error!
}
Lo cierto es que estamos acostumbrados a C++, de manera que una clase derivada no es más que los atributos de la clase base, y, a continuación, los atributos de la clase derivada. Incluso Glib, pese a utilizar C y no C++, utiliza este "truco" en el que la clase base está primero en la memoria con la clase derivada. Como el puntero a la clase base tiene asociado un tamaño, el de la propia clase base, aunque apunte a un objeto de la clase derivada, solo puede acceder a los atributos de la clase base.
Un ejemplo específico en C: si tenemos una estructura Cat en C, podemos tener como primer miembro una estructura base Animal, y apuntar con un puntero de esta clase base al registro de Cat, con lo que solo podemos acceder a los atributos de la clase Animal. Como decía, así es, muy básicamente, como funciona GObject sobre Glib.
#include <stdbool.h>
#include <string.h>
#include <stdio.h>
#define MaxName 80
#define MaxColor 25
typedef struct _Animal {
char name[MaxName + 1];
char color[MaxColor + 1];
} Animal;
typedef struct _Cat {
Animal base;
bool purrs;
} Cat;
int main()
{
Cat c;
strncpy( c.base.name, "Panchito", MaxName );
strncpy( c.base.color, "White", MaxColor );
c.purrs = true;
Animal * a = (Animal *) &c;
printf( "\n'%s' is a cat of '%s' color", c.base.name, c.base.color );
printf( "\n'%s' is an animal of '%s' color", a->name, a->color );
return 0;
}
La salida es una simulación de herencia, pero en C.
$ make cinh
cc cinh.c -o cinh
$ ./cinh
'Panchito' is a cat of 'White' color
'Panchito' is an animal of 'White' color
Así que, si tenemos el mismo caso en Go:
type Animal struct {
name string
color string
}
type Cat struct {
Animal
purrs bool
}
¿Es lo mismo? Bueno... hay algunas diferencias. En primer lugar, Animal en Cat no tiene por qué ser el primer miembro. No somos nosotros los que tenemos que "preparar" el mapa en memoria, sino que es el propio compilador quien lo hace. Además, no tenemos que hacer referencia a los miembros de Animal en Cat, sino que podemos hacerlo directamente.
Vale, pero ¿cómo podemos crear un method factory? Pues la respuesta son las interfaces. No es necesario indicar en una estructura que implementa cierta interfaz, sino que es el propio compilador el que comprueba si la estructura implementa la interfaz cuando es necesario.
En el siguiente ejemplo, tenemos otra vez al Animal y a Cat, y la función New() devuelve una u otra dependiendo de si ronronea (purrs) o no (es un ejemplo como otro cualquiera). La razón por la que ahora funciona es que en lugar de devolver una u otra estructura, ahora devolvemos una interfaz. La interfaz está definida justo al principio, y como todas las interfaces, se basa en los métodos que declara cada estructura. En el momento en el que se retorna Animal o Cat, el compilador comprueba si ambas estructuras cumplen la interfaz o no.
package main
import "fmt"
type AnimalLike interface {
Name() string
Color() string
String() string
}
type Animal struct {
name string
color string
}
func (a *Animal) Name() string {
return a.name
}
func (a *Animal) Color() string {
return a.color
}
func (a* Animal) String() string {
return fmt.Sprintf("'%s' is an animal of color '%s'", a.Name(), a.Color())
}
type Cat struct {
Animal
purrs bool
}
func (c *Cat) Purrs() bool {
return c.purrs
}
func (c* Cat) String() string {
purrs := ""
if c.Purrs() {
purrs = ", and purrs."
}
return fmt.Sprintf("'%s' is a cat of color '%s'%s", c.Name(), c.Color(), purrs)
}
func New(name, color string, purrs bool) AnimalLike {
var toret AnimalLike
if purrs {
toret = &Cat{Animal: Animal{name: name, color: color}, purrs: true}
} else {
toret = &Animal{name: name, color: color}
}
return toret
}
func main() {
fmt.Printf("\n%s", New("Panchito", "white", true))
fmt.Printf("\n%s", New("Panchito", "white", false))
}
La salida es la esperada:
'Panchito' is a cat of color 'white', and purrs.
'Panchito' is an animal of color 'white'
Más o menos esta es una introducción muy somera a Go. En la siguiente entrega, veremos los errores que, a mi juicio, presenta.
Top comments (0)