DEV Community

Go... interface!

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!
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Así que, si tenemos el mismo caso en Go:

type Animal struct {
    name string
    color string
}

type Cat struct {
    Animal
    purrs bool
}
Enter fullscreen mode Exit fullscreen mode

¿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))
}
Enter fullscreen mode Exit fullscreen mode

La salida es la esperada:

'Panchito' is a cat of color 'white', and purrs.
'Panchito' is an animal of color 'white'
Enter fullscreen mode Exit fullscreen mode

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)